[{"body":"","link":"https://blog.sksoumik.com/","section":"","tags":null,"title":""},{"body":"","link":"https://blog.sksoumik.com/tags/agentic-ai/","section":"tags","tags":null,"title":"agentic ai"},{"body":"","link":"https://blog.sksoumik.com/tags/artificial-intelligence/","section":"tags","tags":null,"title":"artificial intelligence"},{"body":"","link":"https://blog.sksoumik.com/categories/artificial-intelligence/","section":"categories","tags":null,"title":"artificial intelligence"},{"body":"","link":"https://blog.sksoumik.com/categories/","section":"categories","tags":null,"title":"Categories"},{"body":"Large Language Models are good at understanding text and generating answers.\nBut there is an important problem.\nBy default, an LLM does not truly remember everything that happened before.\nImagine you talk to an AI assistant today and tell it:\nI prefer short answers and I usually work with Python.\nTomorrow, you start a new conversation.\nIf the system has no memory, the assistant may know nothing about that previous conversation.\nFor a simple chatbot, this may be acceptable.\nFor an AI agent, it becomes a serious limitation.\nAn agent may need to remember:\nwhat it learned about the user what happened during previous tasks what actions worked before what instructions it should follow what mistakes it made what happened in previous conversations This is why memory is becoming an important part of Agentic AI systems.\nIn this article, we will look at three useful types of memory:\nProcedural memory\nSemantic memory\nEpisodic memory\nThe names may sound complicated.\nThe ideas are actually quite simple.\nFirst, What Is an AI Agent? A normal LLM application may look like this:\n1User 2 ↓ 3Prompt 4 ↓ 5LLM 6 ↓ 7Answer For example:\n1User: 2Explain machine learning. 3 4LLM: 5Machine learning is a method that allows computers... An AI agent usually does more.\nIt may:\ncall APIs search databases use tools read documents make decisions perform several steps remember previous information continue tasks over time A simplified agent may look like this:\n1 User 2 ↓ 3 Agent 4 ↓ 5 ┌────────┼────────┐ 6 ↓ ↓ ↓ 7 LLM Tools Memory 8 ↓ ↓ ↓ 9 └────────┼────────┘ 10 ↓ 11 Action Memory gives the agent access to information from the past.\nWithout memory, every interaction can feel like meeting someone who forgets you immediately after the conversation ends.\nWhy Do Agents Need Memory? Imagine you have an AI travel assistant.\nOn Monday, you tell it:\n1I prefer window seats. 2 3I do not like very early flights. 4 5I usually travel with one checked bag. On Tuesday, you ask:\n1Find me a flight to Singapore. A useful assistant should remember your preferences.\nNow imagine another situation.\nLast month, the assistant booked a flight for you.\nThe process was:\n1Search flights 2 3Compare prices 4 5Check baggage 6 7Ask user for confirmation 8 9Book flight When doing another booking, the agent may benefit from remembering how that previous task happened.\nThere is also information such as:\n1Singapore is a country in Southeast Asia. That is different from remembering something that happened to you personally.\nThese different kinds of information are why memory systems are often divided into categories.\nA useful way to think about them is:\n1Procedural Memory 2How should I do something? 3 4Semantic Memory 5What do I know? 6 7Episodic Memory 8What happened before? Let us look at each one.\n1. Semantic Memory Semantic memory is memory about facts, knowledge, meanings, and information.\nThink about facts you know.\nFor example:\n1Dhaka is the capital of Bangladesh. 2 3Python is a programming language. 4 5The user prefers dark mode. 6 7The user\u0026#39;s company uses Google Cloud. 8 9The project uses PostgreSQL. These are pieces of information.\nThey are not stories about something that happened.\nThey are simply things the system knows.\nThat is semantic memory.\nA Human Example You probably know:\n1Paris is the capital of France. You may not remember when you first learned it.\nYou may not remember who taught you.\nYou simply know the fact.\nThat is similar to semantic memory.\nSemantic Memory in an AI Agent Suppose you build an AI assistant for an employee.\nOver time, the agent learns:\n1User\u0026#39;s preferred programming language: Python 2 3User\u0026#39;s team: Data Science 4 5Preferred cloud provider: Google Cloud 6 7Main database: BigQuery 8 9Preferred response style: Short explanations The assistant can store these facts as semantic memory.\nLater, the user says:\n1Write an example for me. Instead of choosing JavaScript, the agent may use Python because it remembers the user's preference.\nSemantic Memory Does Not Need to Come Only From the User Semantic memory can come from many places.\nFor example:\n1Conversation 2 3Documents 4 5Databases 6 7Tool results 8 9User profile 10 11Previous agent runs Suppose an agent discovers:\n1Project Alpha uses PostgreSQL. That fact may be useful for many future tasks.\nThe system could save it.\nLater, the user asks:\n1Write a query for Project Alpha. The agent already knows which database the project uses.\nHow Semantic Memory Might Be Stored A simple memory record might look conceptually like this:\n1Fact: 2User prefers Python. 3 4Category: 5Programming preference 6 7Confidence: 8High 9 10Created: 11August 2026 Another might be:\n1Fact: 2Project Alpha uses PostgreSQL. 3 4Category: 5Project infrastructure 6 7Confidence: 8High These facts could be stored in:\n1SQL database 2 3Document database 4 5Vector database 6 7Key value store 8 9Dedicated memory service The exact storage system is less important than the idea.\nThe goal is to save useful facts and retrieve them when needed.\nSemantic Memory Can Change Some facts are permanent.\nOthers change.\nSuppose the agent stores:\n1User works on Project Alpha. Six months later:\n1User moved to Project Beta. Now the old memory may be incorrect.\nA good memory system needs to handle this.\nIt may update the fact:\n1Old: 2Current project = Alpha 3 4New: 5Current project = Beta This is why real memory systems often need information such as:\n1Creation time 2 3Last update time 4 5Confidence 6 7Source 8 9Expiration time Memory is not only about saving information.\nIt is also about deciding whether that information is still useful.\n2. Episodic Memory Episodic memory is memory about events and experiences.\nInstead of remembering a fact, the agent remembers something that happened.\nFor example:\n1Yesterday, the user asked the agent to analyze a CSV file. 2 3The agent found missing values. 4 5The user decided not to remove those rows. 6 7The user asked the agent to use median values instead. That is an episode.\nIt describes an event.\nA Human Example Think about the difference between these two memories.\nSemantic memory:\n1Tokyo is the capital of Japan. Episodic memory:\n1I visited Tokyo in 2024 and stayed near Shinjuku. The first is a fact.\nThe second is something that happened.\nThis distinction is very useful for AI agents.\nEpisodic Memory in an Agent Imagine a coding agent.\nYesterday, it tried to fix a bug.\nIt performed these steps:\n1Read error logs 2 3Inspected database connection 4 5Changed retry logic 6 7Ran tests 8 9Tests failed 10 11Reverted the change 12 13Found the actual issue in configuration That whole experience could become an episodic memory.\nLater, another similar error happens.\nThe agent can retrieve that previous episode.\nIt may realize:\n1A similar error happened before. 2 3The retry logic was not the problem. 4 5The configuration was the real cause. This can help the agent make a better decision.\nEpisodic Memory Is Like an Agent's History You can think of episodic memory as a collection of stories.\nEach story might contain:\n1What happened? 2 3When did it happen? 4 5Who was involved? 6 7What was the goal? 8 9What actions were taken? 10 11What was the result? 12 13What was learned? For example:\n1Episode: 2 3Goal: 4Deploy model to production. 5 6Action: 7Agent created a deployment. 8 9Problem: 10Health check failed. 11 12Cause: 13Wrong port configuration. 14 15Solution: 16Changed application port from 8000 to 8080. 17 18Result: 19Deployment succeeded. This memory may later help with another deployment.\nWhy Episodic Memory Is Powerful for Agents Agents often perform multi step tasks.\nSome tasks succeed.\nSome fail.\nIf every new task starts from zero, the agent cannot learn much from its previous experiences.\nEpisodic memory makes something closer to this possible:\n1Previous experience 2 ↓ 3Retrieve similar episode 4 ↓ 5Understand what happened 6 ↓ 7Use that knowledge 8 ↓ 9Make a better decision This is especially useful for:\ncoding agents research agents customer support agents personal assistants business workflow agents autonomous systems Episodic Memory Can Be Summarized There is another challenge.\nSuppose an agent has completed 100,000 tasks.\nYou probably do not want to store every single token from every task forever.\nThat can become expensive.\nInstead, the system might create a shorter memory.\nOriginal interaction:\n125,000 tokens Stored episode:\n1Goal: 2Fix payment API issue. 3 4Cause: 5Expired API credential. 6 7Action: 8Generated a new credential and updated the service. 9 10Result: 11Payment service recovered. 12 13Important lesson: 14Check credential expiration before changing application code. Now the agent keeps the useful part of the experience without keeping every detail.\n3. Procedural Memory Procedural memory is about how to do something.\nThink about instructions, processes, rules, skills, and workflows.\nFor example:\n1When deploying the service: 2 31. Run tests. 4 52. Build the container. 6 73. Push the container. 8 94. Deploy to staging. 10 115. Run smoke tests. 12 136. Deploy to production. This is procedural memory.\nIt tells the agent how something should be done.\nA Human Example You know how to ride a bicycle.\nYou do not normally think:\n1Move left foot. 2 3Now move right foot. 4 5Turn handlebar three degrees. 6 7Maintain balance. You have learned a procedure.\nAnother simpler example is making tea.\n1Boil water. 2 3Add tea. 4 5Wait. 6 7Add milk. 8 9Serve. That is knowledge about how to perform a task.\nProcedural Memory in an AI Agent Imagine an AI agent working inside a software company.\nThe company has a rule:\n1Never deploy directly to production. 2 3First deploy to staging. 4 5Run automated tests. 6 7Get approval. 8 9Then deploy to production. The agent needs to remember this process.\nThat is procedural memory.\nAnother example:\n1When reviewing a pull request: 2 3Read the requirements. 4 5Check the implementation. 6 7Run tests. 8 9Check security issues. 10 11Check performance issues. 12 13Write review comments. Again, this is a procedure.\nWhere Procedural Memory Comes From Procedural memory may come from:\n1System prompts 2 3Company rules 4 5Agent instructions 6 7Workflow definitions 8 9Previous successful procedures 10 11Human feedback 12 13Standard operating procedures Sometimes developers directly write these instructions.\nFor example:\n1When a user asks for a refund: 2 3Verify the order. 4 5Check refund eligibility. 6 7Ask for confirmation. 8 9Call the refund API. 10 11Send confirmation. The agent follows this procedure every time.\nProcedural Memory Can Also Improve Over Time Imagine an agent initially uses this process:\n1Search all documents. 2 3Read every result. 4 5Generate answer. Later, the team discovers a better process:\n1Search documents. 2 3Rank results. 4 5Read the top five. 6 7Check whether enough evidence exists. 8 9Generate answer. The procedure has improved.\nThe new version can replace the previous version.\nThis is similar to improving a skill.\nThe Difference Between the Three Types Let us use one simple example.\nImagine an AI assistant helps you order food.\nSemantic Memory The agent remembers:\n1The user likes spicy food. 2 3The user prefers Thai food. 4 5The user\u0026#39;s favorite restaurant is Restaurant A. These are facts.\nEpisodic Memory The agent remembers:\n1Last Friday, the user ordered Pad Thai from Restaurant A. 2 3The delivery arrived 40 minutes late. 4 5The user complained about the delay. This is an event.\nProcedural Memory The agent remembers:\n1When ordering food: 2 3Check whether the restaurant is open. 4 5Check delivery time. 6 7Show the price. 8 9Ask for confirmation. 10 11Place the order. This is a process.\nA simple way to remember them is:\n1Semantic = What I know 2 3Episodic = What happened 4 5Procedural = How I do it How They Work Together The real power comes when an agent uses all three.\nImagine you tell your assistant:\n1Book a hotel for my next trip to Singapore. The agent may use semantic memory:\n1User prefers hotels near public transport. 2 3User\u0026#39;s usual budget is $150 per night. 4 5User prefers quiet rooms. Then it may use episodic memory:\n1During the previous Singapore trip, the user stayed near Orchard Road. 2 3The user complained that the area was too busy. Then it may use procedural memory:\n1Search hotels. 2 3Filter by budget. 4 5Check location. 6 7Check reviews. 8 9Compare options. 10 11Ask user before booking. Now the agent has:\n1Facts 2+ 3Experience 4+ 5Process That creates a much more useful assistant.\nA Simple Agent Memory Architecture A simplified architecture might look like this:\n1 User Request 2 ↓ 3 Agent 4 ↓ 5 Memory Retrieval 6 ↓ 7 ┌───────────────┼───────────────┐ 8 ↓ ↓ ↓ 9 Semantic Episodic Procedural 10 Memory Memory Memory 11 ↓ ↓ ↓ 12 └───────────────┼───────────────┘ 13 ↓ 14 LLM 15 ↓ 16 Decision 17 ↓ 18 Action 19 ↓ 20 Memory Creation The interesting part is that memory works in both directions.\nBefore the agent acts:\n1Retrieve memory After the agent acts:\n1Create or update memory This creates a loop.\nThe Agent Memory Loop A useful mental model is:\n1Observe 2 ↓ 3Retrieve 4 ↓ 5Think 6 ↓ 7Act 8 ↓ 9Learn 10 ↓ 11Store 12 ↓ 13Observe again Suppose the user says:\n1Do not schedule meetings before 10 AM. The agent may extract:\n1User meeting preference: 2After 10 AM That becomes semantic memory.\nLater, the user says:\n1Schedule a meeting with Sarah tomorrow. The agent retrieves the preference and avoids early morning times.\nAfter the meeting is scheduled, it might create an episodic memory:\n1Scheduled meeting with Sarah for Tuesday at 11 AM. The system now remembers both the preference and the event.\nShould We Store Everything? No.\nThis is one of the hardest problems in agent memory.\nImagine the user says:\n1Thanks. Should that become a permanent memory?\nProbably not.\nImagine the user says:\n1I am moving to Singapore next month. That may be much more useful.\nA memory system therefore needs to decide:\n1Is this information important? 2 3Will it be useful later? 4 5Is it temporary? 6 7Is it already stored? 8 9Does it conflict with another memory? Saving every message creates too much noise.\nSaving nothing makes the agent forgetful.\nThe goal is to store useful information.\nMemory Importance One approach is to give memories an importance score.\nFor example:\n1User prefers Python. 2 3Importance: 0.9 1User said thanks. 2 3Importance: 0.1 The system may only save memories above a certain level.\nThe exact scoring method depends on the application.\nMemory Confidence Not every piece of information is equally reliable.\nSuppose a user says:\n1I think our database might be PostgreSQL. The agent should not treat this exactly the same as:\n1Our production database is PostgreSQL. The first statement is uncertain.\nA memory record could include:\n1Fact: 2Production database may be PostgreSQL. 3 4Confidence: 5Low Later, the agent receives stronger evidence:\n1Production database is PostgreSQL. Now confidence can increase.\nMemory Freshness Some information becomes less useful over time.\nSuppose the agent remembers:\n1User is currently working on Project A. Two years later, this may no longer be correct.\nMemory systems can use time when ranking memories.\nFor example:\n1Relevance 2+ 3Similarity 4+ 5Importance 6+ 7Freshness A newer relevant memory may be preferred over an older one.\nMemory Retrieval Storing memory is only half the problem.\nThe agent also needs to find the right memory later.\nImagine the system stores 100,000 memories.\nThe agent cannot put all 100,000 into the prompt.\nIt needs to retrieve a small number of useful memories.\nSuppose the user asks:\n1What database should I use for Project Alpha? The memory system might search for memories related to:\n1Project Alpha 2 3database 4 5infrastructure Then it may retrieve:\n1Project Alpha uses PostgreSQL. The LLM receives that information with the user's question.\n1Relevant Memory: 2 3Project Alpha uses PostgreSQL. 4 5User: 6 7What database should I use for Project Alpha? Now the model can give a better answer.\nVector Search and Memory Semantic search is commonly useful for memory retrieval.\nSuppose a stored memory says:\n1The user usually writes backend services in Python. Later, the user asks:\n1Which language should we use for this API? The words are different.\nBut the meaning is related.\nA vector search system can help find memories based on meaning instead of exact word matching.\nA simplified flow looks like:\n1Memory 2 ↓ 3Embedding 4 ↓ 5Vector Database Later:\n1User Request 2 ↓ 3Embedding 4 ↓ 5Similarity Search 6 ↓ 7Relevant Memories Those memories can then be added to the context sent to the LLM.\nMemory Is Different From Conversation History This is an important difference.\nConversation history may contain everything:\n1User message 2 3Assistant answer 4 5User message 6 7Assistant answer 8 9User message 10 11Assistant answer Memory usually contains selected useful information.\nFor example, a conversation may contain 10,000 tokens.\nThe memory system may extract:\n1User prefers Python. 2 3User is working on Project Alpha. 4 5Project Alpha uses PostgreSQL. Instead of sending the entire conversation every time, the system retrieves only relevant memories.\nThis can reduce context size and make the agent more focused.\nMemory Is Also Different From RAG Memory systems and Retrieval Augmented Generation can look similar.\nBoth may use:\n1Embeddings 2 3Vector search 4 5Retrieval 6 7Context injection But their purpose is often different.\nRAG usually retrieves external knowledge.\nFor example:\n1Company documentation 2 3Research papers 4 5Product manuals 6 7Policies Memory usually represents things the agent learned through interactions or previous experiences.\nFor example:\n1User preferences 2 3Previous decisions 4 5Past agent actions 6 7Previous failures 8 9Learned facts A real agent can use both.\n1 Agent 2 ↓ 3 ┌──────────┴──────────┐ 4 ↓ ↓ 5 RAG Memory 6 ↓ ↓ 7 Documents and User facts, 8 external knowledge experiences, 9 procedures Short Term Memory and Long Term Memory You may also hear the terms:\nShort term memory\nand\nLong term memory\nThese describe how long information is available.\nThey are different from semantic, episodic, and procedural memory.\nFor example:\nSemantic memory can be long term.\n1User prefers Python. Episodic memory can also be long term.\n1User deployed Project Alpha last month. Short term memory may contain information needed only during the current task.\n1Current file being edited: 2payment.py After the task ends, the system may discard it.\nSo you can think about memory in two different dimensions.\n1What kind of information is it? 2 3Semantic 4Episodic 5Procedural And:\n1How long should we keep it? 2 3Short term 4Long term A Realistic Example Imagine we build an AI software engineering agent.\nThe user asks:\n1Deploy the payment service. The agent retrieves procedural memory:\n1Deployment procedure: 2 3Run tests. 4 5Build container. 6 7Deploy to staging. 8 9Run smoke tests. 10 11Get approval. 12 13Deploy to production. It retrieves semantic memory:\n1Payment service runs on Google Cloud. 2 3Repository is payment service. 4 5Production region is us central1. It retrieves episodic memory:\n1Last deployment failed because PORT was set incorrectly. 2 3Correct application port is 8080. Now the agent has everything it needs.\n1Procedural 2How should I deploy? 3 4Semantic 5What do I know about this service? 6 7Episodic 8What happened during previous deployments? The agent runs the deployment.\nAfterward, it may save another episode:\n1Deployment completed successfully. 2 3Version 2.4.1 deployed. 4 5All smoke tests passed. The memory system keeps growing.\nFuture tasks can use those experiences.\nMemory Can Also Create Problems Memory sounds very useful.\nBut badly designed memory can make an agent worse.\nThere are several common problems.\nProblem 1: Wrong Memories Suppose the system incorrectly stores:\n1User prefers Java. But the user actually prefers Python.\nThe agent may continue making bad decisions because it trusts the incorrect memory.\nMemory systems therefore need ways to update and delete information.\nProblem 2: Old Memories Suppose:\n12025: 2Project uses MySQL. 3 42026: 5Project migrated to PostgreSQL. If the agent retrieves the older memory, it may give the wrong answer.\nMemory needs versioning, timestamps, or freshness rules.\nProblem 3: Too Many Memories Imagine storing every small interaction.\n1Memory 1 2 3Memory 2 4 5Memory 3 6 7... 8 9Memory 5,000,000 Retrieval becomes harder.\nMore memory does not automatically mean better intelligence.\nMemory quality matters more than memory quantity.\nProblem 4: Duplicate Memories Imagine the system stores:\n1User likes Python. 2 3User prefers Python. 4 5Python is user\u0026#39;s preferred language. 6 7User generally chooses Python. These are almost the same memory.\nDuplicates waste storage and can influence retrieval unfairly.\nGood systems usually need some form of duplicate detection.\nProblem 5: Privacy Memory can contain personal information.\nThat means memory systems should have clear rules around:\n1What can be stored? 2 3How long can it be stored? 4 5Can the user delete it? 6 7Who can access it? 8 9Should sensitive information be stored at all? Memory is powerful, so it should be handled carefully.\nA Good Memory System Needs More Than a Database It is tempting to think:\nI will create a vector database. Now my agent has memory.\nThat is only one piece.\nA useful memory system usually needs several steps.\n1Conversation 2 ↓ 3Memory Extraction 4 ↓ 5Importance Check 6 ↓ 7Duplicate Check 8 ↓ 9Memory Storage 10 ↓ 11Memory Retrieval 12 ↓ 13Ranking 14 ↓ 15LLM Context You also need decisions about:\n1When to create memory 2 3What to store 4 5When to update memory 6 7When to remove memory 8 9How to rank memories 10 11How many memories to retrieve The database itself does not solve these problems.\nA Simple Mental Model If you remember only one part of this article, remember this:\n1Semantic Memory 2What do I know? 3 4Episodic Memory 5What happened? 6 7Procedural Memory 8How do I do it? You can also think about a human employee.\nA good employee knows facts about the company.\nThat is semantic memory.\nThey remember what happened during previous projects.\nThat is episodic memory.\nThey know how company processes should be followed.\nThat is procedural memory.\nAn AI agent becomes more useful when it can work with similar types of information.\nPutting Everything Together A mature agent memory system might look conceptually like this:\n1 User 2 ↓ 3 AI Agent 4 ↓ 5 Understand Task 6 ↓ 7 Retrieve Memory 8 ↓ 9 ┌──────────────┼──────────────┐ 10 ↓ ↓ ↓ 11 Semantic Episodic Procedural 12 Memory Memory Memory 13 ↓ ↓ ↓ 14 └──────────────┼──────────────┘ 15 ↓ 16 LLM Reasoning 17 ↓ 18 Use Tools 19 ↓ 20 Action 21 ↓ 22 Observe Result 23 ↓ 24 Extract Learning 25 ↓ 26 Update Memories This creates a system that does more than simply answer prompts.\nIt can use past information to make future decisions.\nFinal Takeaway LLMs are powerful, but an agent becomes much more useful when it can remember.\nThe three memory types provide a simple way to organize what the agent remembers.\nSemantic memory stores facts and knowledge.\n1User prefers Python. Episodic memory stores experiences and events.\n1Last deployment failed because the application port was incorrect. Procedural memory stores instructions and processes.\n1Before production deployment, run tests and deploy to staging. Together, they answer three important questions:\n1What do I know? 2 3What happened before? 4 5How should I do this? That is the core idea behind memory in Agentic AI.\nThe goal is not to make an agent remember everything.\nThe goal is to help it remember the right information, at the right time, for the right task.\n","link":"https://blog.sksoumik.com/artificial-intelligence/memory-systems-agentic-ai/","section":"artificial-intelligence","tags":["artificial intelligence","agentic ai","system design"],"title":"Memory Systems in Agentic AI: Procedural, Semantic, and Episodic Memory"},{"body":"","link":"https://blog.sksoumik.com/artificial-intelligence/","section":"artificial-intelligence","tags":["index"],"title":"Posts"},{"body":"","link":"https://blog.sksoumik.com/tags/system-design/","section":"tags","tags":null,"title":"system design"},{"body":"","link":"https://blog.sksoumik.com/categories/system-design/","section":"categories","tags":null,"title":"system design"},{"body":"","link":"https://blog.sksoumik.com/tags/","section":"tags","tags":null,"title":"Tags"},{"body":" Hello, I am Soumik, pronounced Shou mik.\nI am a Senior Data Scientist at Optimizely Inc.. I build production artificial intelligence (AI) systems that go beyond training models. My work includes multi agent systems, large language model (LLM) applications, retrieval augmented generation (RAG) systems, recommender systems, evaluation pipelines, and machine learning infrastructure.\nI have seven years of industry experience in artificial intelligence and machine learning. I enjoy the engineering around a model, including memory, application programming interfaces (APIs), data pipelines, deployment, monitoring, and the product decisions that make a system useful and dependable.\nMy work today Some of the systems I work on at Optimizely include:\nMulti purpose agentic AI systems and memory components that preserve user and chat context over long periods. Large scale recommendation systems that serve millions of user interactions every day. A/B testing systems for evaluating and deploying machine learning models. Real time training pipelines that learn from user interactions. Machine learning pipelines that train thousands of models each day. Conversational AI systems for customer segmentation and analysis. Unified API services for different large language model providers. Retrieval augmented generation systems that give AI applications domain specific knowledge. Experience Senior Data Scientist at Optimizely Inc., January 2023 to present. I work remotely with teams building AI and machine learning products for a digital experience platform. Senior Artificial Intelligence Engineer at Venturas Ltd., June 2021 to December 2023. I designed AI and machine learning systems, guided development teams, and worked as a Tech Lead for a period. Machine Learning Engineer at Chowa Giken Corporation, September 2019 to June 2021. This was the start of my professional career after I completed my bachelor degree. Education and location I completed a Bachelor of Science in Computer Science and Engineering at North South University in Dhaka from 2015 to 2019.\nI am based in Dhaka, Bangladesh, and open to relocation.\nThings I build outside work I enjoy taking a product from an idea to a working product. I make the product decisions, design the architecture, write the code, deploy the product, and improve it based on real user feedback.\nbdtechjobs.com, a curated job board for technology opportunities in Bangladesh. Jot, a private budget tracker for iPhone, iPad, and Apple Watch. It has no account, no advertisements, and no bank connection. Data stays on your devices, with optional iCloud sync. Voice Writter, an on device voice to text app for macOS with automatic grammar correction. It uses Whisper and a local MLX language model. Cycle, a private period tracker for iOS. It has no account, no analytics, and no third party software development kits. Tools and technologies I work with Python, TypeScript, SQL, Swift, C, C++, LangGraph, LangChain, the OpenAI software development kit (SDK), Hugging Face, PyTorch, Keras, scikit learn, XGBoost, Pandas, Polars, Apache Spark, Google Cloud Platform, Amazon Web Services, Docker, Kubernetes, Terraform, Airflow, MLflow, FastAPI, Django, Next.js, React, SwiftUI, UIKit, and SwiftData.\nI also work with embeddings, retrieval augmented generation, model context protocol (MCP), and tools such as Claude Code, Cursor, and Codex.\nWriting and contact I write on this blog about machine learning systems, artificial intelligence, software engineering, infrastructure, and the lessons I learn while building and operating products.\nYou can visit my portfolio, connect with me on LinkedIn, or see my work on GitHub.\nEmail: sadmanks@gmail.com\nDisclaimer This blog is a place for me to share my thoughts and learnings from daily life and work. These are my personal notes. I do not claim to be an expert in every topic I write about. I am a learner who likes to share what I learn.\nMy notes may not be complete or fully accurate because they are not formal research. They reflect my current understanding and can be used as a starting point for further study. Please read them with an open mind. I am always learning, and I may make mistakes.\nThank you for visiting my site. If you have any questions or concerns, please feel free to contact me.\n","link":"https://blog.sksoumik.com/about/","section":"","tags":null,"title":"About Me"},{"body":"You have downloaded an open source Large Language Model.\nMaybe it is Llama, Qwen, Gemma, or another model.\nYou load it on an NVIDIA GPU and send a prompt:\n1What is the capital of Bangladesh? The model answers:\n1The capital of Bangladesh is Dhaka. Everything works.\nBut then 100 users start sending requests at the same time.\nSuddenly things become harder.\nSome requests wait.\nGPU memory fills up.\nYour expensive GPU may not be used efficiently.\nResponse time increases.\nThis is where vLLM becomes useful.\nvLLM is an inference and serving engine designed to run Large Language Models efficiently. It focuses heavily on GPU utilization, memory management, scheduling, and serving many requests at the same time.\nIn this article, we will understand how vLLM works without going too deep into CUDA or GPU programming.\nFirst, What Exactly Is vLLM? One important thing to understand is this:\nvLLM is not an LLM.\nLlama is an LLM. Qwen is an LLM. Gemma is an LLM.\nvLLM is the system that runs and serves those models efficiently.\nThink about a restaurant.\nThe LLM is the chef. The GPU is the kitchen. vLLM is the restaurant manager.\nThe manager decides:\nWho gets served first?\nWhich orders can be prepared together?\nHow should the kitchen space be used?\nWhen should a new customer enter?\nHow can we avoid doing the same work twice?\nA good chef with a badly managed restaurant can still serve customers slowly.\nThe same idea applies to GPUs.\nYou can have a powerful A100 or H100, but poor inference software can still waste much of its potential. vLLM tries to solve that problem.\nWhat Happens Without a Good Serving Engine? Imagine three users send requests.\n1User A 2 3Explain machine learning. 4 5 6User B 7 8Write a Python function. 9 10 11User C 12 13Summarize this document. These requests can have very different sizes.\nUser A might generate 100 tokens. User B might generate 500 tokens. User C might send a 10,000 token document and generate 300 tokens.\nThis creates several problems.\nThe requests do not start at the same time.\nThey do not finish at the same time.\nThey use different amounts of GPU memory.\nTheir prompts have different lengths.\nTheir answers have different lengths.\nManaging all of this efficiently is difficult.\nvLLM contains a scheduler and memory management system designed specifically for this type of workload. Modern vLLM also supports continuous batching, PagedAttention, prefix caching, chunked prefill, optimized GPU execution, quantization, and speculative decoding.\nThe Big Picture A simplified vLLM request looks like this:\n1User 2 ↓ 3API Server 4 ↓ 5Tokenizer 6 ↓ 7vLLM Scheduler 8 ↓ 9KV Cache Manager 10 ↓ 11Model Runner 12 ↓ 13GPU 14 ↓ 15Generated Tokens 16 ↓ 17User There are many details inside each part, but this simplified picture is enough to understand the main idea.\nNow let us follow one request through the system.\nStep 1: The Request Arrives Imagine your application sends this request:\n1Explain neural networks in simple language. vLLM can expose an API that follows the OpenAI API protocol, which makes it easier to connect existing applications to a locally hosted or privately hosted model.\nYour application sends the text to the vLLM server.\nBut the model cannot directly understand words.\nThe text first needs to become tokens.\nStep 2: vLLM Converts Text Into Tokens Suppose we have:\n1Machine learning is amazing. A tokenizer might convert this into something conceptually similar to:\n1Machine 2learning 3is 4amazing 5. Internally these tokens are represented using numbers.\nFor example:\n1[14523, 6975, 374, 8056, 13] The exact numbers depend on the model.\nNow vLLM has something the model can process. But it does not necessarily send the request straight to the GPU.\nFirst, something very important happens.\nThe scheduler looks at the requests.\nStep 3: The Scheduler Decides What the GPU Should Work On Imagine the GPU is a factory.\nMany jobs are waiting outside.\n1Request A 2Request B 3Request C 4Request D 5Request E The factory has limited capacity.\nYou cannot simply throw everything inside. Someone needs to decide what should run. That is one of the scheduler's jobs.\nThe vLLM scheduler decides which requests and how many tokens should be processed during the next execution step.\nIt also works with the KV cache manager to determine whether enough memory is available.\nThink of it as a traffic controller.\n1 Scheduler 2 ↓ 3 ┌─────────────┼─────────────┐ 4 ↓ ↓ ↓ 5 Request A Request B Request C 6 ↓ 7 GPU The scheduler continuously makes these decisions while requests enter and leave the system.\nThis is one reason vLLM is useful when many users are accessing the same model.\nStep 4: The Model Processes the Prompt Now imagine the user sends:\n1Explain how photosynthesis works. Before generating an answer, the model needs to process the prompt.\nThis stage is usually called prefill.\nSuppose the prompt contains 2,000 tokens.\nThe model processes those input tokens and builds information that will be useful when generating the answer.\nSome of this information is stored in something called the KV cache.\nTo understand why vLLM is fast, we need to understand the KV cache.\nWhat Is the KV Cache? Large Language Models generate text one token at a time.\nImagine the model generates:\n1Artificial Then:\n1Artificial intelligence Then:\n1Artificial intelligence is Then:\n1Artificial intelligence is changing When generating the next token, the model needs information about the tokens that came before it.\nWithout caching, the model would have to repeat a lot of previous calculations.\nThat would be wasteful.\nInstead, transformer models store useful information about previous tokens.\nThis information is called the KV cache.\nKV means:\nKey\nand\nValue\nYou do not need to understand the mathematics behind keys and values to understand vLLM.\nThe important part is this:\nThe KV cache helps the model avoid repeating attention calculations for tokens it has already processed.\nThe problem is that the KV cache can consume a large amount of GPU memory, especially when many requests or long sequences are being processed. The original vLLM research identified KV cache memory management as a major limitation for high throughput LLM serving.\nAnd this leads us to one of the most important ideas in vLLM.\nPagedAttention PagedAttention is one of the ideas that made vLLM well known.\nTo understand it, imagine we have GPU memory like this:\n1GPU Memory 2 3████████████████████████████████ Every request needs some space for its KV cache.\nThe problem is that we do not know exactly how much space each request will eventually need.\nA user might generate 50 tokens.\nAnother might generate 500.\nAnother might generate 5,000.\nTraditional memory allocation can therefore waste space.\nImagine a parking lot where every car receives a parking area large enough for a bus.\n1Car A 2 3[ ] 4 5Car B 6 7[ ] 8 9Car C 10 11[ ] Most of the space is empty.\nThat is inefficient.\nPagedAttention takes a different approach.\nInstead of treating each request as needing one large continuous area of memory, vLLM divides KV cache memory into smaller blocks. Those blocks can be allocated when they are needed.\nConceptually:\n1GPU KV Cache 2 3[Block 1] 4[Block 2] 5[Block 3] 6[Block 4] 7[Block 5] 8[Block 6] 9[Block 7] 10[Block 8] Request A might use:\n1Block 1 2Block 4 3Block 7 Request B might use:\n1Block 2 2Block 3 The blocks do not need to sit next to each other in physical memory.\nThis is similar to the idea of paging in operating systems.\nThat is where the name PagedAttention comes from.\nThe result is better memory usage.\nBetter memory usage means vLLM can often fit more active requests into the same GPU memory.\nAnd that can increase throughput.\nWhy Memory Efficiency Matters So Much Suppose your GPU has enough memory for:\n1Model weights 2 3plus 4 510 active requests If memory is managed more efficiently, maybe you can support:\n1Model weights 2 3plus 4 520 active requests The exact numbers depend on the model and workload.\nBut the general idea is simple.\nMore efficient memory management allows more useful work to happen on the same hardware.\nThe original vLLM paper reported significant throughput improvements compared with the serving systems tested by the researchers, especially for longer sequences and larger models.\nStep 5: Continuous Batching Keeps the GPU Busy Now we reach another major idea.\nContinuous batching.\nImagine four requests enter the server.\n1Request A 2Request B 3Request C 4Request D Traditional batching might put them together.\n1Batch 2 3A 4B 5C 6D But suppose Request A finishes quickly.\nRequest B is still running.\nRequest C is still running.\nRequest D is still running.\nIn a simple batching system, the empty space created by Request A may not immediately be used by another request.\nThat is wasteful.\nContinuous batching works differently.\nWhen Request A finishes, another waiting request can enter.\nFor example:\n1Before 2 3A 4B 5C 6D A finishes.\nThen:\n1E 2B 3C 4D Later C finishes.\nThen:\n1E 2B 3F 4D Requests continuously enter and leave the active group.\nvLLM lists continuous batching as one of its main serving optimizations.\nThink again about our restaurant.\nA normal batching system might say:\nWe will wait until everyone at every table finishes before seating new customers.\nContinuous batching says:\nA table is free. Seat the next customer immediately.\nThis helps keep the GPU busy.\nStep 6: The Model Generates Tokens After the prefill stage, generation begins.\nSuppose the answer is:\n1Photosynthesis allows plants to convert light into energy. The model generates something conceptually like this:\n1Photosynthesis Then:\n1allows Then:\n1plants Then:\n1to Then:\n1convert And so on.\nThis stage is commonly called decode.\nAfter every generation step, vLLM updates the KV cache.\nThe scheduler then decides what work should happen during the next step.\nThe process repeats.\n1Schedule requests 2 ↓ 3Run model 4 ↓ 5Generate tokens 6 ↓ 7Update KV cache 8 ↓ 9Schedule again 10 ↓ 11Run model again 12 ↓ 13Generate more tokens 14 ↓ 15... This loop continues until each request finishes.\nStep 7: Tokens Can Be Streamed Back to the User Users usually do not want to wait for the entire answer.\nInstead, applications often stream tokens.\nThe user sees:\n1Photosynthesis Then:\n1Photosynthesis allows Then:\n1Photosynthesis allows plants And the answer continues appearing.\nThis creates the familiar ChatGPT style experience where text appears gradually.\nThe model may still be generating the rest of the response while the user is already reading the beginning.\nSo What Is vLLM Really Doing? At this point, we can simplify the entire system.\nvLLM is mainly trying to answer three questions again and again.\nQuestion 1 Which requests should the GPU process right now?\nThe scheduler helps answer this.\nQuestion 2 Where should the KV cache for those requests live?\nPagedAttention and the KV cache manager help answer this.\nQuestion 3 How can we keep the GPU doing useful work?\nContinuous batching and optimized model execution help answer this.\nThat is the heart of vLLM.\nThere are many advanced features around these ideas, but if you understand these three questions, you understand much of the reason vLLM exists.\nPrefix Caching vLLM can also reuse KV cache information when requests share the same prefix. In current vLLM V1, automatic prefix caching is managed through the KV cache manager.\nImagine an AI agent where every request starts with this:\n1You are a customer support assistant. 2 3Follow these instructions. 4 5You have access to these 30 tools. 6 7Here are the tool definitions. 8 9Here are 20 examples. Suppose this is 8,000 tokens.\nThen User A asks:\n1Where is my order? User B asks:\n1Cancel my subscription. User C asks:\n1Change my email address. The first 8,000 tokens may be identical.\nWithout caching:\n1User A → Process 8,000 tokens 2 3User B → Process 8,000 tokens 4 5User C → Process 8,000 tokens With prefix caching, vLLM can reuse KV cache blocks from previously processed prefixes when they match.\nConceptually:\n1Common 8,000 token prefix 2 ↓ 3 Cached once 4 ↓ 5 ┌─────┼─────┐ 6 ↓ ↓ ↓ 7 User A User B User C For applications with large repeated system prompts, this can save a lot of repeated work.\nChunked Prefill Another useful feature is chunked prefill.\nImagine one request contains a huge prompt:\n130,000 input tokens Processing that entire prompt can require a lot of computation.\nMeanwhile, other users may already be waiting for their next generated token.\nChunked prefill allows vLLM to split large prefill work into smaller pieces and schedule that work alongside decode requests.\nInstead of thinking:\n1Process all 30,000 tokens first. Think:\n1Process part of the long prompt. 2 3Generate tokens for existing requests. 4 5Process another part. 6 7Generate more tokens. 8 9Continue. This can help balance throughput and interactive response latency.\nWhat Happens When GPU Memory Becomes Full? Imagine the GPU KV cache is almost full.\nBut new requests continue arriving.\nvLLM cannot create unlimited GPU memory.\nWhen KV cache space becomes insufficient, vLLM can preempt some requests so that memory becomes available for other work. Those requests can later be recomputed when capacity becomes available.\nThis is another reason the scheduler and KV cache manager are closely connected.\nThe scheduler cannot only think about computation.\nIt also needs to think about memory.\nvLLM Does Not Only Use PagedAttention People sometimes explain vLLM like this:\n1vLLM = PagedAttention That is too simple.\nPagedAttention is important, but modern vLLM contains many other optimizations.\nThese include continuous batching, chunked prefill, prefix caching, optimized GPU kernels, CUDA graph support, quantization, speculative decoding, and different forms of parallel execution.\nSo a better mental model is:\n1 vLLM 2 │ 3 ┌─────────────┼─────────────┐ 4 │ │ │ 5 Scheduler Memory System GPU Execution 6 │ │ │ 7 Continuous KV Cache Optimized 8 Batching Management Kernels 9 │ 10 PagedAttention 11 │ 12 Prefix Caching All these pieces work together.\nAn Example With Multiple Users Imagine we are serving a model on an A100.\nFive users arrive.\n1User A → 2,000 token prompt 2 3User B → 500 token prompt 4 5User C → 7,000 token prompt 6 7User D → 1,000 token prompt 8 9User E → waiting vLLM's scheduler chooses work from the active requests.\nThe KV cache manager allocates blocks for their KV caches.\nPagedAttention allows those caches to use GPU memory in blocks.\nThe GPU processes tokens for multiple requests.\nThen User B finishes.\nIts KV cache blocks can eventually become available again.\nUser E can enter the active workload.\nMeanwhile, User C still has a large prompt, so chunked prefill may allow its prompt processing to be mixed with generation work from other requests.\nConceptually:\n1Time 1 2 3A B C D 4 5 6Time 2 7 8A B C D 9 10 11Time 3 12 13B finishes 14 15 16Time 4 17 18A E C D 19 20 21Time 5 22 23D finishes 24 25 26Time 6 27 28A E C F This constant movement is why continuous batching is so useful.\nThe server does not treat inference as one fixed batch.\nIt treats inference as a continuously changing workload.\nWhy Is vLLM Faster Than a Simple Model Server? There is an important distinction here. vLLM does not magically make the neural network smaller. It does not make Llama suddenly require half as many transformer layers.\nInstead, vLLM tries to reduce waste around running the model.\nThink about a supermarket.\nImagine ten checkout counters.\nA poorly managed supermarket might have:\n1Counter 1 → busy 2 3Counter 2 → empty 4 5Counter 3 → empty 6 7Counter 4 → huge queue 8 9Counter 5 → empty The supermarket owns enough hardware.\nThe problem is scheduling.\nvLLM tries to manage expensive GPU resources more intelligently.\nThe improvement comes from things such as:\nBetter KV cache memory usage.\nMore requests running together.\nLess wasted GPU capacity.\nBetter scheduling.\nReuse of cached computation.\nOptimized GPU execution.\nThroughput and Latency Are Different When discussing vLLM, it is useful to understand two different goals.\nLatency Latency means:\nHow long does one user wait?\nFor example:\n1Request sent 2 ↓ 31.2 seconds 4 ↓ 5First token appears Throughput Throughput means:\nHow much total work can the system process?\nFor example:\n15,000 generated tokens every second or:\n1100 requests every second vLLM is especially designed around efficient, high throughput serving while still supporting interactive workloads.\nSometimes increasing throughput can hurt individual request latency.\nThat is why settings such as batch size, token budget, KV cache size, and parallelism should be tuned for your actual workload.\nA Simple Mental Model If you remember only one thing from this article, remember this analogy.\nImagine your GPU is a hotel.\nThe LLM weights occupy a large part of the hotel permanently.\nRequests are guests.\nThe KV cache is the room space each guest needs.\nThe scheduler is the hotel manager.\nPagedAttention divides available space into smaller manageable units.\nContinuous batching means new guests can enter whenever capacity becomes available.\nPrefix caching allows useful shared information to be reused.\nChunked prefill prevents one enormous guest from taking over too much service capacity at once.\nvLLM coordinates all of this.\nWithout good management, an expensive hotel can still serve very few guests.\nWith good management, the same hotel can serve many more.\nThe Full Request Flow Now we can put everything together.\n1Application 2 ↓ 3Request arrives 4 ↓ 5Text becomes tokens 6 ↓ 7Scheduler receives request 8 ↓ 9KV cache blocks are allocated 10 ↓ 11Prompt is processed 12 ↓ 13KV cache is stored 14 ↓ 15Model generates next token 16 ↓ 17KV cache is updated 18 ↓ 19Scheduler creates the next group of work 20 ↓ 21More requests enter through continuous batching 22 ↓ 23GPU runs the next model execution 24 ↓ 25More tokens are generated 26 ↓ 27Tokens are streamed back 28 ↓ 29Request finishes 30 ↓ 31KV cache blocks become reusable This happens continuously for many requests.\nWhere PagedAttention Fits If you see diagrams of vLLM, PagedAttention can sometimes appear to be the entire system.\nIt is better to think about it like this:\n1 vLLM 2 ↓ 3 Scheduler 4 ↓ 5 KV Cache Manager 6 ↓ 7 PagedAttention 8 ↓ 9 GPU Memory PagedAttention is mainly about making attention and KV cache memory management work efficiently with paged blocks.\nThe scheduler is responsible for deciding what should run.\nThe model runner performs the model computation.\nContinuous batching keeps changing the active group of requests.\nTogether, these parts create the serving system.\nWhat About Speculative Decoding? vLLM also supports speculative decoding. The current documentation describes it as a technique that can reduce the time between generated tokens for certain workloads, especially some workloads with lower request volume where inference is limited by memory movement.\nThe idea is simple.\nNormally:\n1Large model 2 3Token 1 4 ↓ 5Token 2 6 ↓ 7Token 3 8 ↓ 9Token 4 With speculative decoding, another mechanism predicts several possible tokens.\n1Draft 2 3Token 1 4Token 2 5Token 3 6Token 4 7 8 ↓ 9 10Large model checks them If several predictions are correct, generation can move forward faster.\nThis is an additional optimization.\nIt is not the basic reason vLLM works.\nWhat vLLM Does When You Start the Server At startup, vLLM has several important jobs.\nIt loads the model.\nIt loads the tokenizer.\nIt prepares GPU memory.\nIt determines KV cache capacity.\nIt creates the serving engine.\nIt prepares the model runner.\nThen it waits for requests.\nOnce traffic arrives, the repeating process begins:\n1Receive 2 ↓ 3Schedule 4 ↓ 5Allocate memory 6 ↓ 7Run model 8 ↓ 9Generate 10 ↓ 11Update cache 12 ↓ 13Schedule again This loop may happen many times every second.\nThe Main Idea Behind vLLM The easiest way to understand vLLM is not to think about one clever algorithm.\nThink about resource management.\nLLM serving has three expensive resources:\n1GPU Compute 2 3GPU Memory 4 5Time vLLM tries to use all three efficiently.\nPagedAttention helps with memory.\nContinuous batching helps with GPU utilization.\nThe scheduler coordinates requests.\nThe KV cache avoids unnecessary repeated attention calculations.\nPrefix caching can avoid repeated work across requests.\nChunked prefill helps schedule long prompts alongside generation.\nOptimized kernels and GPU execution help perform the actual computations efficiently.\nThat combination is what makes vLLM useful.\nFinal Takeaway You can summarize vLLM in one sentence:\nvLLM is a serving engine that tries to keep GPUs busy while using GPU memory intelligently.\nThe most important concepts to remember are:\nScheduler: decides what work happens next.\nKV cache: stores useful information from previously processed tokens.\nPagedAttention: manages KV cache memory using smaller blocks.\nContinuous batching: continuously adds and removes requests from active GPU work.\nPrefix caching: reuses computation when requests share the same beginning.\nChunked prefill: splits large prompt processing into smaller pieces.\nOnce you understand those ideas, vLLM becomes much easier to understand.\nThe model itself is still doing the same fundamental job:\n1Input tokens 2 ↓ 3Transformer 4 ↓ 5Predict next token vLLM's job is to make sure that this process happens efficiently when many real users are asking the model for answers at the same time.\nThat is the difference between simply running an LLM and building an efficient LLM serving system.\n","link":"https://blog.sksoumik.com/artificial-intelligence/how_vllm_works_for_serving_llms/","section":"artificial-intelligence","tags":["artificial intelligence","large language models","software engineering","system design"],"title":"How vLLM Works for Serving Large Language Models"},{"body":"","link":"https://blog.sksoumik.com/tags/large-language-models/","section":"tags","tags":null,"title":"large language models"},{"body":"","link":"https://blog.sksoumik.com/tags/software-engineering/","section":"tags","tags":null,"title":"software engineering"},{"body":"","link":"https://blog.sksoumik.com/categories/software-engineering/","section":"categories","tags":null,"title":"software engineering"},{"body":"Running a Large Language Model is easy.\nRunning it fast, at scale, and without wasting expensive GPUs is much harder.\nImagine you deploy an open source LLM on an NVIDIA A100 GPU. Your API works, but users sometimes wait several seconds before they see the first word. When many users arrive at the same time, things get even slower.\nBuying more GPUs is one solution.\nBut it is often not the best first solution.\nThere are many ways to make LLM inference much faster using the GPUs you already have.\nIn this article, I will explain the most useful techniques in simple language.\nFirst, What Is LLM Inference? When you send a prompt to an LLM and it generates an answer, that process is called inference.\nFor example:\n1User: 2What is the capital of Bangladesh? 3 4LLM: 5The capital of Bangladesh is Dhaka. Everything the model does after receiving the question is part of inference.\nThere are two important stages.\n1. Prefill First, the model needs to read and understand your input.\nImagine your prompt contains 5,000 tokens.\nThe model needs to process those 5,000 tokens before it can start generating the answer.\nThis is called prefill.\nLong prompts usually mean more prefill work.\n2. Decode After processing the prompt, the model starts generating new tokens.\nIt might generate:\n1The 2The capital 3The capital of 4The capital of Bangladesh 5The capital of Bangladesh is 6The capital of Bangladesh is Dhaka This stage is called decode.\nThe model generates tokens one after another.\nThese two stages behave differently on a GPU. That becomes important when we start optimizing inference.\n1. Use an Inference Engine One of the biggest mistakes is serving a production LLM using a basic PyTorch or Hugging Face setup.\nIt works, but it may not use your GPU efficiently.\nInstead, use an inference engine designed specifically for serving LLMs.\nTwo popular choices are:\nvLLM\nand\nTensorRT LLM\nThese engines contain many optimizations for running LLMs efficiently on GPUs.\nThink about it like this.\nYou bought a Ferrari. But you are driving it through city traffic at 30 km/h. The problem is not the car. The problem is how you are using it.\nYour A100 is extremely powerful. A good inference engine helps you use much more of that power.\nFor many teams, vLLM is a great place to start because it is relatively easy to deploy and already contains several important optimizations.\n2. Use Continuous Batching Suppose four users send requests to your LLM.\n1User A 2User B 3User C 4User D A simple server might process them inefficiently.\nIt may wait for one group of requests to finish before starting another group.\nThat means some GPU capacity can sit unused.\nContinuous batching solves this problem.\nThe inference server keeps adding new requests whenever GPU capacity becomes available.\nImagine a restaurant.\nWithout continuous batching, the restaurant might say:\nWe will not seat anyone new until everyone currently eating has finished.\nThat would waste many empty tables.\nWith continuous batching, whenever a table becomes free, another customer can immediately use it.\nThis allows the GPU to process many requests efficiently.\nIf your LLM receives many requests at the same time, continuous batching can make a very large difference.\n3. Use Paged Attention LLMs need memory while generating text.\nOne important part of this memory is called the KV cache.\nThe KV cache can become very large, especially when you have:\nlong conversations large prompts many users large context windows Traditional memory management can waste GPU memory.\nvLLM introduced an approach called PagedAttention.\nThe basic idea is similar to how operating systems manage computer memory.\nInstead of requiring one large continuous area of memory for every request, memory can be divided into smaller blocks.\nThis makes memory usage much more efficient.\nMore efficient memory means you can usually serve more requests using the same GPU.\n4. Use Prefix Caching This is especially useful for AI agents.\nImagine every request starts with the same system prompt:\n1You are an AI assistant for our company. 2 3Follow these 50 instructions. 4 5Here are 30 available tools. 6 7Here are their schemas. 8 9Here are several examples. Imagine this system prompt contains 6,000 tokens.\nThen the user asks:\n1What were my recent orders? Another user asks:\n1Cancel my latest order. The first 6,000 tokens might be exactly the same.\nWithout caching, the model processes those same tokens again and again.\nThat is wasted computation.\nPrefix caching allows the inference engine to reuse previous computation for repeated prefixes.\nInstead of:\n1Process 6,000 tokens 2Process 6,000 tokens 3Process 6,000 tokens 4Process 6,000 tokens you can reuse some of the work.\nThis can significantly improve Time to First Token, especially when your application has large repeated system prompts.\nFor agent systems with large tool definitions, prefix caching should be one of the first things you test.\n5. Quantize the Model LLMs contain billions of numbers called parameters. Those numbers need to be stored in GPU memory and accessed during inference.\nFor example, a model might normally use:\n1FP16 You may be able to represent the model using lower precision formats such as:\n1INT8 2 3or 4 54 bit This process is called quantization.\nA simple analogy is image compression.\nA high quality photo might require 20 MB.\nA compressed version might require only 4 MB while still looking almost identical.\nQuantization does something conceptually similar with model weights.\nSmaller weights mean:\nless GPU memory less memory movement potentially faster inference more room for KV cache potentially more concurrent users But there is a tradeoff.\nAggressive quantization can reduce model quality. So always test the model after quantization.\nDo not only ask:\nIs it faster?\nAlso ask:\nIs the model still good enough?\n6. Use Speculative Decoding LLMs normally generate tokens one after another.\nImagine the model wants to generate:\n1Machine learning is changing software development. The process is roughly:\n1Machine 2↓ 3learning 4↓ 5is 6↓ 7changing 8↓ 9software 10↓ 11development Every step requires computation.\nSpeculative decoding tries to speed this up.\nIt uses two models:\na small, fast model that writes a short draft the large model that decides which tokens are actually used Think of a senior engineer working with a junior engineer.\nInstead of the senior engineer writing everything from scratch, the junior engineer prepares a draft.\nThe senior engineer checks the draft and keeps the parts that are right.\nFor example, the small model might propose these next four tokens:\n1learning is changing software The large model checks all four tokens in one pass.\nThere are two possible outcomes:\nIf it agrees with all four tokens, the system can use all four at once. If it agrees with only learning is, it keeps those tokens. The large model supplies the next token itself, then a new round begins. Without speculative decoding, the large model would need a separate expensive step for each token. With a good draft, one large-model check can move the response forward by several tokens.\nThe large model still controls the final answer. Speculative decoding does not make it smarter or lower its quality. It only avoids waiting for the large model to generate every token one by one.\nSpeculative decoding can reduce the time required to generate output.\nHowever, it does not improve every workload.\nIt works best when the small model often makes the same predictions as the large model. If the draft is often wrong, the large model rejects more tokens and the speedup becomes small.\nYou should benchmark it with your actual traffic.\n7. Be Careful With Tensor Parallelism Suppose you have four A100 GPUs.\nIt may seem obvious that:\n14 GPUs = 4 times faster Unfortunately, it does not always work like that.\nYou can divide one model across several GPUs.\nThis is called tensor parallelism.\nFor example:\n1One model layer, split across four GPUs 2 3[ Part 1 ] [ Part 2 ] [ Part 3 ] [ Part 4 ] 4 ↓ ↓ ↓ ↓ 5 GPU 1 GPU 2 GPU 3 GPU 4 This is useful when the model is too large for one GPU.\nBut now those GPUs need to communicate with each other.\nCommunication takes time.\nIf your model already fits comfortably on one A100, keeping one full model copy on each GPU can sometimes provide better total throughput than splitting one model across all four GPUs.\n1Four separate full model copies 2 3GPU 1 → Full model 4GPU 2 → Full model 5GPU 3 → Full model 6GPU 4 → Full model instead of:\n1One model split across four GPUs 2 3GPU 1 → Model part 1 4GPU 2 → Model part 2 5GPU 3 → Model part 3 6GPU 4 → Model part 4 There is no universal answer.\nBenchmark different configurations.\nHere, TP means tensor parallelism. The number tells you how many GPUs run one model together.\nFor example:\n1TP = 1 → One GPU runs the model 2TP = 2 → Two GPUs run the model together 3TP = 4 → Four GPUs run the model together Then compare latency and throughput.\n8. Use Chunked Prefill for Large Prompts Imagine one user sends a 30,000 token prompt.\nAt the same time, several other users are already generating answers.\nProcessing that huge prompt can consume a lot of GPU compute.\nThis can make other requests slower.\nChunked prefill breaks large prompts into smaller pieces.\nInstead of processing:\n130,000 tokens as one huge piece, the inference engine can process smaller chunks.\nIt can then mix this work with token generation for other requests.\nThis is especially useful when your system has both long prompts and interactive requests.\n9. Do Not Use Huge Context Windows Unless You Need Them Suppose your model supports 128,000 tokens.\nThat does not mean every request needs 128,000 tokens.\nLarge context windows can increase memory requirements significantly.\nLook at your real production traffic.\nMaybe you discover:\n150% of requests \u0026lt; 2,000 tokens 2 390% of requests \u0026lt; 8,000 tokens 4 599% of requests \u0026lt; 20,000 tokens That information should influence how you configure your inference server.\nDo not optimize your entire infrastructure around a theoretical maximum that almost nobody uses.\n10. Optimize Your Prompts Sometimes the easiest inference optimization does not involve CUDA, GPUs, or inference engines.\nSimply send fewer tokens.\nImagine your system prompt contains 12,000 tokens.\nAfter reviewing it, you discover that 4,000 tokens are unnecessary.\nNow every request has 8,000 input tokens instead of 12,000.\nThat is 4,000 fewer tokens for the model to process.\nIf you process millions of requests, this becomes a huge amount of saved computation.\nThis is particularly important for AI agents because tool definitions, examples, memory, retrieved documents, and instructions can make prompts very large.\nBefore buying more GPUs, inspect what you are actually sending to the model.\n11. Optimize Routing Imagine you have four replicas of your model.\n1Request 2 ↓ 3Load Balancer 4 ↓ 5┌─────┬─────┬─────┬─────┐ 6│GPU 1│GPU 2│GPU 3│GPU 4│ 7└─────┴─────┴─────┴─────┘ A normal load balancer might simply send requests to whichever replica looks available.\nBut LLM inference has another consideration: cache locality.\nSuppose GPU 1 already cached a large system prompt.\nA new request uses exactly the same system prompt.\nSending that request to GPU 1 may allow you to reuse cached computation.\nSending it to GPU 3 may require processing everything again.\nAt larger scale, intelligent routing can therefore improve both latency and GPU efficiency.\nWhat Should We Measure? This is extremely important.\nDo not simply say:\nThe model feels faster.\nMeasure it.\nFor LLM inference, I would track at least these metrics.\nTime to First Token How long does the user wait before seeing the first generated token?\nFor interactive applications, this is extremely important.\nTime Per Output Token After generation starts, how quickly do new tokens appear?\nTokens Per Second How many tokens can your system process or generate every second?\nRequests Per Second How many requests can the system handle?\nGPU Utilization Are your expensive A100 GPUs actually busy?\nIf you are paying for A100s while GPU utilization stays at 20 percent, you probably have an optimization opportunity.\nKV Cache Utilization How much of your available KV cache capacity are you actually using?\nP50, P95, and P99 Latency Average latency alone can hide serious problems.\nYour average request might take two seconds while some users wait ten seconds.\nPercentiles help you find these slow requests.\nPutting Everything Together A strong production setup might look something like this:\n1 User Requests 2 ↓ 3 Vertex AI Endpoint 4 ↓ 5 Smart Routing 6 ↓ 7 vLLM / TensorRT LLM 8 ↓ 9 Continuous Batching 10 ↓ 11 Paged Attention 12 ↓ 13 Prefix Caching 14 ↓ 15 Chunked Prefill 16 ↓ 17 Quantized Model Weights 18 ↓ 19 Speculative Decoding 20 ↓ 21 A100 GPUs You do not need to implement everything at once.\nStart with the changes that are most likely to matter.\nA practical order would be:\nFirst: Use a proper inference engine such as vLLM or TensorRT LLM.\nSecond: Enable continuous batching and efficient KV cache management.\nThird: Enable prefix caching if your prompts share large common prefixes.\nFourth: test quantization.\nFifth: benchmark different tensor parallel configurations.\nSixth: test speculative decoding.\nSeventh: optimize routing when you have many replicas.\nAnd throughout the entire process, measure everything.\nThe Most Important Lesson Making LLM inference faster is not simply about buying faster GPUs.\nA powerful GPU running an inefficient inference stack can still perform poorly.\nThe goal is to make better use of the hardware you already have.\nSometimes a software optimization can give you more improvement than adding another expensive GPU.\nSo before asking:\nShould we add more A100s?\nAsk:\nAre we actually using our current A100s efficiently?\nThat question can save both milliseconds and money.\n","link":"https://blog.sksoumik.com/artificial-intelligence/how-to-make-llm-inference-faster/","section":"artificial-intelligence","tags":["artificial intelligence","large language models","system design"],"title":"How to Make LLM Inference Faster"},{"body":"","link":"https://blog.sksoumik.com/tags/cloud/","section":"tags","tags":null,"title":"cloud"},{"body":"","link":"https://blog.sksoumik.com/categories/cloud-computing/","section":"categories","tags":null,"title":"cloud computing"},{"body":"","link":"https://blog.sksoumik.com/cloud-computing/","section":"cloud-computing","tags":null,"title":"Cloud-computings"},{"body":"Kubernetes can feel confusing when you first learn it.\nYou hear words like:\nCluster Node Pod Deployment Service Replica Autoscaling Load Balancer Then someone shows you a huge YAML file.\nSuddenly Kubernetes looks much harder than it really is.\nThe basic idea is actually simple.\nKubernetes is a system that helps you run and manage containers across many machines.\nIn Google Cloud, the managed Kubernetes service is called Google Kubernetes Engine, usually called GKE. Google describes GKE as a managed Kubernetes service for deploying containerized applications on Google Cloud.\nIn this article, we will understand Kubernetes using practical examples from Google Cloud.\nWe will also use an LLM inference API as our main example.\nFirst, What Problem Does Kubernetes Solve? Imagine you create an API using Python and FastAPI.\nThe API looks like this:\n1User 2 ↓ 3FastAPI 4 ↓ 5LLM 6 ↓ 7Response You package the application inside a Docker container.\nThen you create a Compute Engine virtual machine on Google Cloud and run the container.\n1Internet 2 ↓ 3Compute Engine VM 4 ↓ 5Docker Container 6 ↓ 7FastAPI 8 ↓ 9LLM Everything works.\nBut your application becomes popular.\nNow thousands of users are sending requests.\nOne server is no longer enough.\nSo you create more machines.\n1 Users 2 ↓ 3 Load Balancer 4 ↓ 5 ┌─────────┼─────────┐ 6 ↓ ↓ ↓ 7 VM 1 VM 2 VM 3 8 ↓ ↓ ↓ 9 Container Container Container Now you have another problem.\nWho manages these machines?\nWhat happens if VM 2 crashes?\nWho starts another application instance?\nWhat happens if traffic becomes ten times larger?\nWho creates more containers?\nWhat happens when traffic decreases?\nWho removes unnecessary resources?\nHow do you update the application without shutting everything down?\nHow do containers find each other?\nThis is the type of problem Kubernetes solves.\nThink of Kubernetes as a Manager Imagine you own a restaurant.\nYou have:\n120 employees 2 3100 tables 4 53 kitchens 6 7Thousands of customers You need a manager to organize everything.\nThe manager decides:\n1Which employee works where? 2 3How many employees are needed? 4 5Who replaces someone who is unavailable? 6 7How should customers be distributed? 8 9When should more staff be added? 10 11When can staff go home? Kubernetes plays a similar role for your applications.\nYou tell Kubernetes what you want.\nFor example:\n1I want 5 copies of my API running. Kubernetes tries to make sure that 5 copies keep running.\nIf one crashes, Kubernetes can create another one.\nThis idea is extremely important.\nDesired State Kubernetes works around something called desired state.\nYou describe what you want.\nFor example:\n1Desired state 2 3Application: recommendation API 4 5Number of copies: 3 Kubernetes looks at reality.\n1Current state 2 3Application copies running: 2 Then Kubernetes notices:\n1Desired = 3 2 3Current = 2 Something is wrong.\nSo Kubernetes creates another copy.\nNow:\n1Desired = 3 2 3Current = 3 Everything matches again.\nThis happens automatically.\nYou usually do not tell Kubernetes every individual action.\nYou describe the state you want.\nKubernetes tries to maintain it.\nKubernetes on Google Cloud You can install and manage Kubernetes yourself.\nBut that requires managing a lot of infrastructure.\nGoogle provides Google Kubernetes Engine.\nGKE manages much of the Kubernetes infrastructure for you. GKE currently supports two main operating modes called Standard and Autopilot. With Autopilot, Google manages more of the underlying infrastructure and configuration.\nA simplified architecture looks like this:\n1 Google Cloud 2 ↓ 3 GKE 4 ↓ 5 Kubernetes Cluster 6 ↓ 7 ┌──────────┼──────────┐ 8 ↓ ↓ ↓ 9 Node 1 Node 2 Node 3 10 ↓ ↓ ↓ 11 Pods Pods Pods Now we need to understand what these words mean.\nWhat Is a Kubernetes Cluster? A cluster is a group of machines managed together by Kubernetes.\nImagine:\n1GKE Cluster 2 3Node 1 4Node 2 5Node 3 6Node 4 These machines provide:\nCPU Memory GPU Network Storage Your applications run inside this cluster.\nIn GKE, Pods run on nodes in your cluster.\nWhat Is a Node? A Node is basically a machine that Kubernetes can use to run applications.\nIn GKE, a node commonly comes from Google Cloud compute infrastructure.\nImagine you have:\n1Node 1 2 3CPU: 8 cores 4Memory: 32 GB Another node might have:\n1Node 2 2 3CPU: 16 cores 4Memory: 64 GB 5GPU: NVIDIA GPU Kubernetes looks at the available resources and decides where workloads should run.\nFor example:\n1Application A needs 2 32 CPU 44 GB RAM Kubernetes might place it on Node 1.\nAnother application might need a GPU.\n1LLM Inference Server 2 38 CPU 440 GB RAM 51 GPU Kubernetes should place that workload on a node that has the required GPU resources.\nWhat Is a Pod? This is one of the most important Kubernetes concepts.\nYour application usually runs inside a Pod.\nA Pod is the smallest unit Kubernetes normally manages.\nThink about it like this:\n1Node 2 ↓ 3Pod 4 ↓ 5Container 6 ↓ 7Your Application Suppose you have a FastAPI application.\n1Pod 2 ↓ 3Docker Container 4 ↓ 5FastAPI Or imagine you run vLLM.\n1Pod 2 ↓ 3Container 4 ↓ 5vLLM 6 ↓ 7LLM A node can run many Pods.\n1Node 1 2 3┌─────────────┐ 4│ Pod A │ 5├─────────────┤ 6│ Pod B │ 7├─────────────┤ 8│ Pod C │ 9└─────────────┘ One important idea is that Pods should usually be treated as replaceable.\nA Pod can disappear.\nAnother Pod can be created.\nYour system should normally not depend on one particular Pod living forever.\nWhy Not Just Run Containers Directly? You might ask:\nWhy do we need Pods? Why not just run Docker containers?\nBecause Kubernetes needs something it can manage.\nA Pod gives Kubernetes a standard unit for things such as:\n1Scheduling 2 3Networking 4 5Storage 6 7Health checking 8 9Resource allocation 10 11Restart behavior For beginners, it is usually enough to remember:\nA Pod is where your application container runs.\nWhat Is a Deployment? Suppose you want your API running in three Pods.\nYou could manually create:\n1Pod 1 2 3Pod 2 4 5Pod 3 But that would create another problem.\nWhat happens when Pod 2 crashes?\nYou would need to create another Pod yourself.\nInstead, Kubernetes gives us something called a Deployment.\nYou can tell the Deployment:\n1Application: 2Recommendation API 3 4Desired replicas: 53 Then Kubernetes tries to maintain three Pods.\n1 Deployment 2 ↓ 3 Desired replicas = 3 4 ↓ 5 ┌─────────┼─────────┐ 6 ↓ ↓ ↓ 7 Pod 1 Pod 2 Pod 3 Suppose Pod 2 crashes.\nNow Kubernetes sees:\n1Expected Pods: 3 2 3Running Pods: 2 Kubernetes creates another Pod.\n1 ┌─────────┼─────────┐ 2 ↓ ↓ ↓ 3 Pod 1 Pod 3 Pod 4 We are back to three.\nThis is one of the biggest benefits of Kubernetes.\nWhat Is a Replica? A replica is basically another copy of your application.\nImagine your API runs in one Pod.\n1Pod 1 2 ↓ 3API You have one replica.\nIf you run three copies:\n1Pod 1 → API 2 3Pod 2 → API 4 5Pod 3 → API You have three replicas.\nWhy would you want multiple replicas?\nBecause one application instance may not handle all traffic.\nMultiple replicas can also improve reliability.\nIf one Pod fails, others may continue serving users.\nA GCP Example Imagine you created an AI application.\nUsers upload text and your model creates embeddings.\nYour architecture might start like this:\n1Users 2 ↓ 3GKE 4 ↓ 5Embedding API Pod Traffic increases.\nNow you run four replicas.\n1 Users 2 ↓ 3 GKE 4 ↓ 5 Deployment 6 ↓ 7 ┌───────────┼───────────┐ 8 ↓ ↓ ↓ 9 Pod 1 Pod 2 Pod 3 10 11 ↓ 12 Pod 4 The Pods all run the same application.\nBut How Do Users Reach the Pods? Now we have another problem.\nPods can be created and destroyed.\nTheir network addresses can change.\nYour user should not need to know:\n1Send this request to Pod 1. Or:\n1Pod 1 disappeared. 2 3Now use Pod 4. Kubernetes solves this using a Service.\nWhat Is a Kubernetes Service? A Service provides a stable way to reach a group of Pods.\nGKE uses Kubernetes Services to group Pod endpoints and make workloads reachable through stable networking behavior.\nThink of a Service like the reception desk of a hotel.\nGuests do not need to know which employee is working.\nThey contact reception.\nReception sends the request to the correct person.\nThe architecture looks like this:\n1 Users 2 ↓ 3 Service 4 ↓ 5 ┌──────────┼──────────┐ 6 ↓ ↓ ↓ 7 Pod 1 Pod 2 Pod 3 Users communicate with the Service.\nThe Service sends traffic toward Pods.\nService Versus Deployment These two concepts are easy to confuse.\nA Deployment answers:\nHow many copies of the application should exist?\nA Service answers:\nHow do other systems reach those application copies?\nSo:\n1Deployment 2 ↓ 3Creates and maintains Pods While:\n1Service 2 ↓ 3Provides access to Pods Together:\n1Users 2 ↓ 3Service 4 ↓ 5Deployment managed Pods 6 ↓ 7Application What About Traffic From the Internet? Imagine you are building:\n1api.example.com Internet traffic needs to reach your GKE application.\nA simplified setup might look like:\n1Internet 2 ↓ 3Google Cloud networking 4 ↓ 5Load Balancer 6 ↓ 7Kubernetes Service 8 ↓ 9Pods Google Cloud can integrate GKE workloads with Google Cloud load balancing.\nThe user does not need to know which Pod actually processes the request.\nWhat Is a Load Balancer? Imagine 10,000 users arrive at your API.\nYou have four Pods.\n1Pod 1 2 3Pod 2 4 5Pod 3 6 7Pod 4 You do not want all requests going to Pod 1.\nA load balancing layer distributes traffic.\nConceptually:\n1 Users 2 ↓ 3 Load Balancer 4 ↓ 5 ┌───────────┼───────────┐ 6 ↓ ↓ ↓ 7 Pod 1 Pod 2 Pod 3 8 ↓ 9 Pod 4 The goal is to spread requests across available application instances.\nKubernetes Self Healing One of the most useful Kubernetes ideas is that applications can recover automatically from some failures.\nSuppose your Deployment requires:\n13 replicas You currently have:\n1Pod A 2 3Pod B 4 5Pod C Then Pod B crashes.\nNow:\n1Pod A 2 3Pod C Kubernetes sees that the current state does not match the desired state.\nSo another Pod is created.\n1Pod A 2 3Pod C 4 5Pod D This does not mean Kubernetes can fix every application bug.\nIf your application code is broken, Kubernetes cannot magically rewrite your code.\nBut Kubernetes can recreate failed workload instances and try to maintain the desired number of Pods.\nWhat Happens When a Node Dies? Now imagine something bigger happens.\nThe entire machine running your Pod disappears.\n1Before 2 3Node 1 4 ↓ 5Pod A 6 7 8Node 2 9 ↓ 10Pod B Node 1 fails.\nPod A disappears with it.\nKubernetes can schedule replacement workload capacity onto available infrastructure.\nConceptually:\n1Node 1 2Failed 3 4 5Node 2 6 ↓ 7Pod B 8 9 10Node 3 11 ↓ 12New Pod A This is one reason Kubernetes is useful for production systems.\nHealth Checks How does Kubernetes know whether your application is healthy?\nApplications can expose health information.\nFor example:\n1Is the application alive? 2 3Is the application ready to receive traffic? Kubernetes has health checking mechanisms that can help it make these decisions.\nImagine your API process exists, but the model is still loading.\n1Container running 2 3Model loading 4 5API not ready You probably do not want user traffic going there yet.\nOnce the model finishes loading:\n1Container running 2 3Model loaded 4 5API ready Now traffic can be sent to it.\nThis becomes especially useful for LLM inference because large models may take time to load.\nKubernetes Scheduling Imagine your cluster has three nodes.\n1Node A 2 34 CPU 416 GB RAM 5 6 7Node B 8 916 CPU 1064 GB RAM 11 12 13Node C 14 1532 CPU 16128 GB RAM 171 GPU Now an LLM Pod needs:\n18 CPU 2 364 GB RAM 4 51 GPU Kubernetes needs to decide where that Pod should run.\nNode A cannot handle it.\nNode B does not have the required GPU.\nNode C does.\nSo the scheduler places the Pod there.\nConceptually:\n1LLM Pod 2 ↓ 3Kubernetes Scheduler 4 ↓ 5Node C 6 ↓ 7GPU This scheduling system becomes extremely useful when you have many workloads.\nResource Requests How does Kubernetes know what your application needs?\nYou can describe resource requirements.\nFor example:\n1Recommendation API 2 3CPU needed: 42 cores 5 6Memory needed: 78 GB Another workload:\n1LLM API 2 3CPU needed: 48 cores 5 6Memory needed: 764 GB 8 9GPU needed: 101 Kubernetes uses this information when deciding where workloads should run.\nCorrect resource configuration is important.\nIf you request far more resources than your application needs, you can waste capacity.\nIf you request too little, performance may suffer.\nNow We Reach One of the Best Kubernetes Features Autoscaling Imagine your application normally receives:\n1100 requests per minute Three Pods are enough.\n1Pod 1 2 3Pod 2 4 5Pod 3 Then something happens.\nTraffic suddenly becomes:\n110,000 requests per minute Three Pods may no longer be enough.\nYou could manually increase replicas.\nOr Kubernetes can automatically respond.\nHorizontal Pod Autoscaling Horizontal scaling means increasing or decreasing the number of Pods.\nGoogle Kubernetes Engine supports Horizontal Pod Autoscaling, which can adjust workload capacity using metrics.\nImagine:\n1Normal traffic 2 3Pod 1 4Pod 2 Traffic increases.\n1High traffic 2 3Pod 1 4Pod 2 5Pod 3 6Pod 4 7Pod 5 8Pod 6 Traffic later decreases.\n1Low traffic 2 3Pod 1 4Pod 2 This can help performance and cost.\nReal Example With an AI API Imagine you run an embedding model.\nNormally:\n12 Pods During business hours, traffic increases.\nCPU usage becomes high.\nThe autoscaler detects increased demand.\nKubernetes increases replicas.\n12 Pods 2 ↓ 34 Pods 4 ↓ 58 Pods When traffic decreases, it can reduce the number again.\n18 Pods 2 ↓ 34 Pods 4 ↓ 52 Pods You do not need eight Pods running all night if nobody is using them.\nLLM Autoscaling on GKE This is not only theoretical.\nGoogle provides guidance for autoscaling LLM inference workloads running on GPUs in GKE. One documented example uses Gemma together with GKE Horizontal Pod Autoscaling.\nImagine you serve an LLM using GPUs.\n1 Users 2 ↓ 3 Service 4 ↓ 5 LLM Deployment 6 ↓ 7 ┌─────────┼─────────┐ 8 ↓ ↓ ↓ 9 LLM Pod LLM Pod LLM Pod 10 ↓ ↓ ↓ 11 GPU GPU GPU When traffic grows, you may want more inference replicas.\nThat is where Kubernetes becomes very useful for AI infrastructure.\nBut What If We Need More Machines? This is different from adding Pods.\nSuppose every node is full.\n1Node 1 2 3FULL 4 5 6Node 2 7 8FULL 9 10 11Node 3 12 13FULL Kubernetes wants to create another Pod.\nBut there is nowhere to put it.\nNow you need more node capacity.\nCluster Autoscaling GKE can automatically resize Standard cluster node pools based on workload demand. When workloads require more capacity, the cluster autoscaler can add nodes. When resources are no longer needed, capacity can be reduced.\nSo there are two different ideas.\nHorizontal Pod Autoscaling:\n1Need more application capacity 2 32 Pods 4 ↓ 56 Pods Cluster Autoscaling:\n1Need more machine capacity 2 33 Nodes 4 ↓ 55 Nodes These can work together.\nA Simple Autoscaling Example Imagine:\n13 Nodes 2 36 Pods Traffic increases.\nThe Pod autoscaler wants:\n112 Pods But the current nodes only have space for 8.\nSo:\n1Horizontal Pod Autoscaler 2 3Requests more Pods 4 ↓ 5Current nodes become full 6 ↓ 7Cluster Autoscaler 8 ↓ 9Adds nodes 10 ↓ 11New Pods can run That is a powerful idea.\nYour infrastructure can respond to changing demand.\nKubernetes for LLM Inference Now let us connect this to LLM serving.\nSuppose you want to serve an open model using vLLM.\nA simplified system might look like:\n1 Users 2 ↓ 3 Cloud Load Balancer 4 ↓ 5 Kubernetes Service 6 ↓ 7 Deployment 8 ↓ 9 ┌────────┼────────┐ 10 ↓ ↓ ↓ 11 Pod 1 Pod 2 Pod 3 12 ↓ ↓ ↓ 13 vLLM vLLM vLLM 14 ↓ ↓ ↓ 15 Model Model Model 16 ↓ ↓ ↓ 17 GPU GPU GPU Each Pod can run an inference server.\nKubernetes manages the Pods.\nGKE manages much of the Kubernetes infrastructure.\nGoogle Cloud provides the compute resources.\nYour application sends requests through the networking layer.\nWhy Kubernetes Is Useful Here LLM inference systems can have unpredictable traffic.\nFor example:\n12 AM 2 320 requests per minute Then:\n12 PM 2 32,000 requests per minute You may need different amounts of capacity.\nKubernetes gives you mechanisms for:\n1Running multiple replicas 2 3Replacing failed Pods 4 5Scheduling workloads onto GPUs 6 7Scaling workloads 8 9Managing networking 10 11Updating applications 12 13Managing configuration This makes it a useful platform for production AI systems.\nWhat Is a Rolling Update? Imagine you currently run:\n1Model API Version 1 You create:\n1Model API Version 2 You do not want to shut down every Version 1 Pod and then start Version 2.\nUsers might experience downtime.\nInstead, Kubernetes Deployments can perform rolling updates, gradually replacing old Pods with updated ones. GKE also allows application Deployment updates through the Google Cloud console or Kubernetes configuration.\nConceptually:\n1Beginning 2 3V1 4V1 5V1 6V1 Then:\n1V1 2V1 3V1 4V2 Then:\n1V1 2V1 3V2 4V2 Then:\n1V1 2V2 3V2 4V2 Finally:\n1V2 2V2 3V2 4V2 The application can remain available while the update happens.\nReal AI Example Suppose your inference server currently runs:\n1Model Version 1 You created a better model:\n1Model Version 2 Instead of replacing every running model server immediately, you can gradually replace application Pods.\nThis reduces the risk of suddenly taking the entire API offline.\nWhat Is a Container Image? We have talked about Pods and Deployments.\nBut where does your application actually come from?\nUsually, you package it as a container image.\nFor example:\n1Python code 2 ↓ 3Dependencies 4 ↓ 5Application configuration 6 ↓ 7Container image In Google Cloud, you can store container images in Artifact Registry.\nA common flow looks like:\n1Source Code 2 ↓ 3Build Container 4 ↓ 5Artifact Registry 6 ↓ 7GKE 8 ↓ 9Pod So GKE knows which application image it should run.\nA Simple Deployment Flow on GCP Imagine you build a recommendation API.\nYour code:\n1Python 2 3FastAPI 4 5Recommendation Model You create a container.\n1Application 2 ↓ 3Container Image You store it in Google Cloud.\n1Container Image 2 ↓ 3Artifact Registry Then deploy it to GKE.\n1Artifact Registry 2 ↓ 3GKE Deployment 4 ↓ 5Pods Users access it through your networking layer.\n1Users 2 ↓ 3Load Balancer 4 ↓ 5Service 6 ↓ 7Pods Now we have a real production architecture.\nWhat Is a ConfigMap? Applications need configuration.\nFor example:\n1LOG LEVEL 2 3SERVICE NAME 4 5FEATURE FLAG 6 7API URL You probably do not want to hardcode all of these directly inside your application.\nKubernetes provides ConfigMaps for storing nonsecret configuration.\nConceptually:\n1ConfigMap 2 3MODEL NAME = recommendation model 4 5LOG LEVEL = info 6 7ENVIRONMENT = production Your Pod can use those values.\nThis allows you to change configuration without rebuilding your entire application image for every small setting.\nWhat About Passwords and API Keys? You should not treat passwords the same way as normal configuration.\nKubernetes provides Secrets for sensitive configuration.\nExamples include:\n1Database password 2 3API token 4 5Private credential In a production GCP system, you may also use Google Cloud services designed for secret management and connect them with your workloads.\nThe important idea is:\n1Normal configuration 2 ↓ 3ConfigMap 4 5 6Sensitive configuration 7 ↓ 8Secret management What Are Namespaces? Imagine your Kubernetes cluster contains many teams.\n1Payments Team 2 3Recommendation Team 4 5Search Team 6 7AI Platform Team Putting everything into one big group becomes messy.\nNamespaces allow you to logically separate resources.\nFor example:\n1Namespace: recommendation 2 3Pods 4 5Services 6 7Deployments Another namespace:\n1Namespace: payments 2 3Pods 4 5Services 6 7Deployments Think of namespaces like folders inside a large workspace.\nThey help organize Kubernetes resources.\nGKE Standard Versus Autopilot GKE gives you different ways to operate Kubernetes.\nTwo important modes are:\n1Standard 2 3Autopilot With Standard mode, you have more control over node infrastructure.\nWith Autopilot, Google manages more of the underlying infrastructure and operational configuration for you.\nA simple mental model is:\n1Standard 2 3More infrastructure control 4More responsibility And:\n1Autopilot 2 3Less infrastructure management 4More management handled by Google Which one you choose depends on your workload.\nFor specialized GPU workloads, networking requirements, or unusual infrastructure needs, you should evaluate both options based on the level of control you need.\nKubernetes Does Not Replace Docker Another common confusion is:\nKubernetes versus Docker.\nThey solve different problems.\nDocker helps package and run containers.\nKubernetes helps manage many containers.\nThink:\n1Docker 2 3How do I package and run this application? Kubernetes:\n1How do I manage thousands of these application containers? They work together.\nKubernetes Does Not Replace GCP Either GCP gives you infrastructure and cloud services.\nKubernetes manages containerized workloads.\nFor example:\n1Google Cloud 2 ↓ 3Provides machines 4networking 5storage 6GPUs 7identity 8monitoring Then:\n1GKE 2 ↓ 3Runs Kubernetes Then:\n1Kubernetes 2 ↓ 3Manages Pods 4Deployments 5Services 6Scaling Then:\n1Your containers 2 ↓ 3Run your application Putting Everything Together Imagine you are building a production LLM API.\nYou use:\n1Python 2 3FastAPI 4 5vLLM 6 7Open model You package everything inside a container.\nThen your architecture might look like this:\n1 Internet 2 ↓ 3 Google Cloud Network 4 ↓ 5 Load Balancer 6 ↓ 7 Kubernetes Service 8 ↓ 9 Deployment 10 ↓ 11 ┌────────────┼────────────┐ 12 ↓ ↓ ↓ 13 Pod 1 Pod 2 Pod 3 14 ↓ ↓ ↓ 15 vLLM vLLM vLLM 16 ↓ ↓ ↓ 17 LLM LLM LLM 18 ↓ ↓ ↓ 19 GPU 1 GPU 2 GPU 3 20 ↓ ↓ ↓ 21 └────────────┼────────────┘ 22 ↓ 23 GKE Cluster Now imagine traffic increases.\n1Traffic increases 2 ↓ 3Autoscaler detects demand 4 ↓ 5More Pods requested 6 ↓ 7More capacity may be needed 8 ↓ 9More node capacity becomes available 10 ↓ 11Additional inference Pods run When traffic decreases:\n1Traffic decreases 2 ↓ 3Fewer replicas needed 4 ↓ 5Pods decrease 6 ↓ 7Unused capacity can decrease This is the type of system Kubernetes is designed to manage.\nThe Most Important Kubernetes Concepts If Kubernetes still feels confusing, remember these simple definitions.\nCluster A group of machines managed by Kubernetes.\n1Cluster 2 3Node 4Node 5Node Node A machine that provides CPU, memory, GPU, and other resources.\n1Node 2 ↓ 3Pods Pod The place where your application container runs.\n1Pod 2 ↓ 3Container 4 ↓ 5Application Deployment Maintains the number of application Pods you want.\n1Deployment 2 ↓ 3Pod 4Pod 5Pod Replica One copy of your application.\n13 replicas 2= 33 application copies Service Provides a stable way to reach a group of Pods.\n1Service 2 ↓ 3Pod 4Pod 5Pod Load Balancer Distributes external traffic toward your application.\n1Internet 2 ↓ 3Load Balancer 4 ↓ 5Application Horizontal Pod Autoscaler Changes the number of Pods based on workload demand.\n12 Pods 2 ↓ 38 Pods Cluster Autoscaler Changes available node capacity when workloads require more or fewer resources.\n13 Nodes 2 ↓ 35 Nodes A Simple Mental Model Think about an apartment building.\nCluster\nThe entire apartment complex.\nNode\nOne apartment building.\nPod\nOne apartment.\nContainer\nThe person living inside the apartment.\nDeployment\nThe manager who makes sure the required number of apartments are occupied.\nService\nThe reception desk that knows how to reach residents.\nLoad Balancer\nThe person distributing incoming visitors.\nAutoscaler\nThe system that adds more capacity when more people arrive.\nThis is not technically perfect, but it is a useful way to remember the concepts.\nWhat Kubernetes Is Really Doing Behind all the terminology, Kubernetes repeatedly asks a few simple questions.\n1What should be running? 2 3What is currently running? 4 5Where should it run? 6 7Is it healthy? 8 9Do we need more copies? 10 11Do we need fewer copies? 12 13How should traffic reach it? That is the core idea.\nYou describe what your system should look like.\nKubernetes continuously tries to make reality match that description.\nFinal Takeaway Do not think of Kubernetes as a tool for running one container.\nDocker can already run one container.\nKubernetes becomes useful when you have a bigger problem.\nFor example:\n1Many containers 2 3Many machines 4 5Many users 6 7Failures 8 9Traffic changes 10 11Application updates 12 13Different resource requirements 14 15GPU workloads 16 17Production reliability On Google Cloud, GKE provides a managed way to run Kubernetes.\nThe easiest way to remember the whole system is:\n1Google Cloud 2 ↓ 3GKE 4 ↓ 5Kubernetes Cluster 6 ↓ 7Nodes 8 ↓ 9Pods 10 ↓ 11Containers 12 ↓ 13Your Application Then add the management pieces:\n1Deployment 2 ↓ 3Keeps the right number of Pods running 4 5 6Service 7 ↓ 8Makes Pods reachable 9 10 11Autoscaling 12 ↓ 13Changes capacity when demand changes 14 15 16Load Balancing 17 ↓ 18Distributes incoming traffic Once you understand this structure, Kubernetes stops looking like a collection of strange words.\nIt becomes a simple idea:\nYou tell Kubernetes how you want your applications to run, and Kubernetes keeps working to maintain that state.\nThat is why Kubernetes is so useful for production systems, including modern AI and LLM infrastructure.\n","link":"https://blog.sksoumik.com/cloud-computing/kubernetes-made-simple/","section":"cloud-computing","tags":["cloud","software engineering"],"title":"Kubernetes Made Simple: How GKE Runs and Scales Applications on GCP"},{"body":"Building one AI agent is relatively easy.\nYou give an LLM some instructions, connect a few tools, and let it perform a task.\nFor example:\n1User 2 ↓ 3Agent 4 ↓ 5LLM 6 ↓ 7Tools 8 ↓ 9Answer But imagine the task becomes much larger.\nYou want an AI system that can:\nUnderstand a complex request Break the request into smaller tasks Search the web Read internal documents Write code Analyze data Ask another agent for help Remember previous work Check its own results Ask a human before taking risky actions Continue working even when one step fails One agent can try to do everything.\nBut as the system becomes more complex, it may make sense to divide the work among several specialized agents.\nThis gives us a multi agent system.\nModern agent frameworks support patterns where a central manager calls specialist agents, or where one agent hands control to another specialist. OpenAI describes both manager based orchestration and agent handoffs as common multi agent patterns.\nBut adding more agents does not automatically make a system better.\nA production multi agent system needs much more than several LLMs talking to each other.\nYou need:\n1Agents 2 3Orchestration 4 5Harness 6 7State 8 9Communication 10 11Tools 12 13Memory 14 15Planning 16 17Routing 18 19Context Management 20 21Guardrails 22 23Evaluation 24 25Human Approval 26 27Observability 28 29Failure Handling In this article, we will understand how all these pieces work together.\nFirst, What Is an AI Agent? Let us start with the simplest definition.\nAn AI agent is usually an LLM connected to instructions and capabilities.\nFor example:\n1 Agent 2 ↓ 3 ┌────────┼────────┐ 4 ↓ ↓ ↓ 5Instructions LLM Tools OpenAI describes an agent as an LLM configured with instructions, tools, and optional runtime behavior such as handoffs and guardrails.\nSuppose we create a database agent.\nIts instruction might be:\n1You are a database expert. 2 3Help users analyze data. 4 5Use the database tools when necessary. 6 7Never modify production data without approval. Its tools might include:\n1search_schema 2 3run_sql 4 5get_table_metadata 6 7explain_query The LLM decides when to use those tools.\nThat is already an agent.\nWhat Is a Multi Agent System? Now imagine we have several agents.\n1Database Agent 2 3Research Agent 4 5Coding Agent 6 7Analytics Agent 8 9Review Agent Each agent has a specific job.\nThe system might look like:\n1 User 2 ↓ 3 Main Agent 4 ↓ 5 ┌──────────────┼──────────────┐ 6 ↓ ↓ ↓ 7 Research Agent Coding Agent Data Agent 8 ↓ ↓ ↓ 9 Tools Tools Tools The main agent does not need to be an expert at everything.\nIt can ask specialist agents for help.\nThink about a company.\nA CEO does not personally write every SQL query, design every page, and fix every server.\nThere are specialists.\n1CEO 2 ↓ 3Engineering 4Marketing 5Finance 6Legal 7Operations A multi agent system uses a similar idea.\nDo You Always Need Multiple Agents? No.\nThis is extremely important.\nIf one agent can reliably complete the task, use one agent.\nMore agents mean more:\n1LLM calls 2 3Latency 4 5Cost 6 7State 8 9Failure possibilities 10 11Communication 12 13Debugging 14 15Evaluation Anthropic recommends starting with simpler systems and adding more autonomous agent behavior only when the task actually requires it. Their agent guidance separates predictable workflows from more autonomous agents and encourages using the simplest design that solves the problem.\nFor example, imagine you want:\n1User asks question 2 ↓ 3Search database 4 ↓ 5Generate answer You probably do not need five agents.\nBut imagine you want:\n1Research a company 2 3Analyze its financial data 4 5Read recent news 6 7Compare competitors 8 9Create charts 10 11Write a report 12 13Review the report 14 15Verify every important claim Now specialization may become useful.\nThe Big Picture A production multi agent system might look something like this:\n1 User 2 ↓ 3 API Layer 4 ↓ 5 Agent Harness 6 ↓ 7 Orchestrator 8 ↓ 9 Router 10 ↓ 11 ┌────────────────┼────────────────┐ 12 ↓ ↓ ↓ 13 Research Data Coding 14 Agent Agent Agent 15 ↓ ↓ ↓ 16 Tools Tools Tools 17 ↓ ↓ ↓ 18 └────────────────┼────────────────┘ 19 ↓ 20 Shared State 21 ↓ 22 Memory 23 ↓ 24 Evaluator 25 ↓ 26 Guardrails 27 ↓ 28 Human Approval 29 ↓ 30 Response Do not worry if this looks complicated.\nWe will go through each piece.\n1. Orchestration The first major problem is orchestration.\nOrchestration answers:\nWho should do what, and in what order?\nImagine a user asks:\n1Research NVIDIA\u0026#39;s latest earnings, 2compare them with AMD, 3analyze the numbers, 4and write a short investment report. We might have three agents:\n1Research Agent 2 3Financial Analysis Agent 4 5Report Agent Someone needs to coordinate them.\nThat is orchestration.\nThe Orchestrator Pattern One common design uses a main agent.\n1 Orchestrator 2 ↓ 3 Understand the task 4 ↓ 5 Create subtasks 6 ↓ 7 ┌───────────────┼───────────────┐ 8 ↓ ↓ ↓ 9 Research Agent Finance Agent Report Agent The orchestrator acts like a manager.\nAnthropic described a similar orchestrator and worker pattern in its multi agent research system, where a lead agent coordinates several worker agents.\nOpenAI also supports a manager style pattern where one agent keeps control and calls specialist agents as tools.\nCode Controlled Orchestration The LLM does not always need to control everything.\nYou can define the workflow in code.\nFor example:\n1Research 2 ↓ 3Analyze 4 ↓ 5Review 6 ↓ 7Write This is predictable.\nYour application decides the order.\nThis works well when you already know the correct process.\nLLM Controlled Orchestration Sometimes the correct process depends on the problem.\nFor example:\n1User: 2Find out why our recommendation system performed worse yesterday. The orchestrator might decide:\n1First inspect metrics. 2 3Then check recent deployments. 4 5Then inspect training data. 6 7Then ask the experimentation agent. 8 9Then compare the results. For another question, it may create a completely different plan.\nOpenAI's orchestration guidance describes both approaches: letting an LLM decide what should happen next, or controlling the workflow directly through application code.\nWhich Should You Use? A simple rule is:\n1Predictable task 2 ↓ 3Code controlled workflow 1Unpredictable task 2 ↓ 3Agent controlled workflow You can also combine them.\nFor example:\n1Code controls the major stages 2 3LLM decides what happens inside each stage This often gives you a useful balance between flexibility and control.\n2. The Agent Harness The agent harness is one of the most important concepts in production agent systems.\nThe LLM is only the brain.\nThe harness is everything around the brain that helps it work.\nThink about a race car driver.\nThe driver may be extremely skilled.\nBut the driver still needs:\n1Car 2 3Steering 4 5Brakes 6 7Dashboard 8 9Radio 10 11Safety system 12 13Navigation 14 15Pit crew The model is the driver.\nThe harness provides the rest.\nA simplified harness might contain:\n1 Agent Harness 2 ↓ 3 ┌────────────────┼────────────────┐ 4 ↓ ↓ ↓ 5 Tools State Memory 6 ↓ ↓ ↓ 7 Planning Context Guardrails 8 ↓ ↓ ↓ 9 Retries Logging Evaluation Anthropic describes agent harnesses as systems around the model that provide capabilities such as tools, context management, planning, and execution support for long running tasks.\nThe quality of the harness can make a huge difference.\nA very strong model with a poor harness may perform worse than a slightly weaker model with excellent tools, context, and controls.\n3. State Management Now imagine an agent is doing a ten step task.\nIt has already completed five steps.\nWhere do we store that information?\nThat is state management.\nSuppose the task is:\n1Build a market analysis report. The state might look conceptually like:\n1Task: 2Market analysis 3 4Status: 5In progress 6 7Completed: 8Company research 9Competitor research 10 11Currently running: 12Financial analysis 13 14Remaining: 15Charts 16Report writing 17Review The system needs this information outside the model.\nWhy Not Keep Everything Inside the Prompt? Because the prompt is not your application database.\nIf the process crashes, you may lose information.\nIf another agent needs the information, sharing it becomes difficult.\nIf the task runs for hours, the context may become too large.\nInstead, keep important state in a proper storage system.\nFor example:\n1PostgreSQL 2 3Redis 4 5Firestore 6 7Spanner 8 9Object Storage Then agents can read and update that state.\nOpenAI's Agents SDK also separates runtime context and resumable run state, and supports saving state when workflows pause for approvals or other interruptions.\nA Shared State Example Imagine three agents.\n1Research Agent 2 3Analysis Agent 4 5Writer Agent Instead of sending huge messages between them, they update shared state.\n1 Shared State 2 ↓ 3 ┌───────────────┼───────────────┐ 4 ↓ ↓ ↓ 5 Research Analysis Writer 6 Agent Agent Agent The research agent writes:\n1research_status = complete 2 3research_document = report_123 The analysis agent reads it and writes:\n1analysis_status = complete 2 3analysis_document = analysis_456 The writer then reads both.\nThis is much cleaner than putting everything into one giant conversation.\n4. Communication Between Agents Now we need agents to communicate.\nThere are several ways to do this.\nMethod 1: Direct Handoff Agent A transfers control to Agent B.\n1User 2 ↓ 3Agent A 4 ↓ 5Agent B 6 ↓ 7User OpenAI calls this a handoff. A handoff allows one agent to delegate the conversation to another specialized agent.\nImagine customer support.\n1General Support Agent 2 ↓ 3\u0026#34;This is a refund issue\u0026#34; 4 ↓ 5Refund Agent Now the Refund Agent takes responsibility.\nMethod 2: Agent as a Tool The main agent stays in control.\n1 Main Agent 2 ↓ 3 calls Research Agent 4 ↓ 5 gets result 6 ↓ 7 calls Finance Agent 8 ↓ 9 gets result 10 ↓ 11 creates answer OpenAI supports this manager pattern by allowing agents to be exposed as tools to another agent.\nThis pattern is useful when you want one agent to own the final answer.\nMethod 3: Shared State Agents communicate indirectly through storage.\n1Agent A 2 ↓ 3Shared State 4 ↓ 5Agent B This works well for longer workflows.\nMethod 4: Events and Queues For larger systems, agents may communicate through events.\nFor example:\n1Research Agent 2 ↓ 3\u0026#34;research.completed\u0026#34; 4 ↓ 5Message Queue 6 ↓ 7Analysis Agent You could use systems such as:\n1Pub/Sub 2 3Kafka 4 5RabbitMQ This can be useful when agents run independently or when tasks take a long time.\nWhat Should Agents Send Each Other? Do not automatically send the entire conversation.\nSend the information the next agent actually needs.\nFor example, instead of:\n150,000 tokens of research history send:\n1Research Summary 2 3Important Findings 4 5Sources 6 7Open Questions 8 9Confidence This saves tokens and keeps the next agent focused.\nAnthropic has described subagents as useful for context isolation because they can work in separate context windows and return only relevant information to the orchestrator.\n5. Tool Access Agents become much more useful when they can take actions.\nA model without tools can mainly generate text.\nA model with tools can do things.\nFor example:\n1Search web 2 3Query database 4 5Read files 6 7Write files 8 9Run Python 10 11Call APIs 12 13Send email 14 15Create ticket 16 17Deploy service OpenAI describes tools as a way for agents to fetch information, run code, call APIs, interact with computers, and perform other actions.\nDifferent Agents Should Have Different Tools Imagine this system:\n1Research Agent 2 3Database Agent 4 5Deployment Agent Do not automatically give every tool to every agent.\nThe Research Agent may need:\n1web_search 2 3read_web_page The Database Agent may need:\n1read_schema 2 3run_sql The Deployment Agent may need:\n1deploy_staging 2 3check_logs 4 5restart_service This makes tool selection easier.\nIt also improves safety.\nThe research agent probably should not have permission to delete a production database.\nTool Design Matters Suppose you give an agent a tool named:\n1execute What does it execute?\nThe model has little information.\nA better tool might be:\n1search_customer_orders with a description explaining:\n1Search customer orders using a customer ID. 2 3Use this when the user asks about previous or current orders. Clear tool names, descriptions, inputs, and outputs help models choose tools more reliably. Anthropic's guidance on agent tools emphasizes that tool interfaces should be designed carefully for the model using them.\n6. Planning Complex tasks often need a plan.\nImagine:\n1Build a competitor analysis for five AI companies. The agent might create:\n11. Identify the companies. 2 32. Collect product information. 4 53. Collect pricing. 6 74. Find recent announcements. 8 95. Compare features. 10 116. Analyze strengths and weaknesses. 12 137. Write the report. 14 158. Review the claims. Now the system has a roadmap.\nWhy Planning Helps Without planning, an agent can jump around.\nIt might:\n1Search one company 2 3Start writing 4 5Realize information is missing 6 7Search again 8 9Rewrite everything 10 11Forget another company A plan gives the task structure.\nPlans Should Be Editable A plan should not always be fixed.\nSuppose the agent discovers:\n1Company B has been acquired. The plan may need to change.\nSo think of planning as:\n1Create plan 2 ↓ 3Execute step 4 ↓ 5Observe result 6 ↓ 7Update plan 8 ↓ 9Execute next step Not:\n1Create plan once 2 ↓ 3Blindly follow forever 7. Routing Routing answers another important question:\nWhich agent should receive this task?\nImagine we have:\n1Coding Agent 2 3Finance Agent 4 5Support Agent 6 7Research Agent The user says:\n1My invoice amount is incorrect. The router decides:\n1Finance Agent Another user says:\n1Fix this Python function. The router chooses:\n1Coding Agent A Router Can Be an LLM You can give an LLM:\n1Available agents: 2 3Coding Agent 4Finance Agent 5Research Agent 6Support Agent Then ask it to select the best one.\nA Router Can Also Be Code Some routing is simple enough for rules.\nFor example:\n1refund request 2 ↓ 3Refund Agent 1account password 2 ↓ 3Account Agent If routing can be reliable with simple logic, you may not need another LLM call.\nRouting Can Be Hierarchical Large systems may have many agents.\nImagine 100 specialist agents.\nOne router should probably not choose directly among all 100.\nYou might use:\n1 Main Router 2 ↓ 3 ┌───────────────┼───────────────┐ 4 ↓ ↓ ↓ 5 Engineering Business Support 6 Router Router Router 7 ↓ ↓ ↓ 8 Specialists Specialists Specialists This creates a hierarchy.\n8. Memory State and memory sound similar, but they solve different problems.\nState usually describes the current task.\nMemory stores useful information from the past.\nFor example:\n1State: 2 3Current task is analyzing Q2 revenue. Memory:\n1The user usually wants revenue broken down by region. Memory can be divided into several useful types.\n1Semantic Memory 2 3Facts and knowledge 1Episodic Memory 2 3Previous events and experiences 1Procedural Memory 2 3How tasks should be performed Agent systems can combine tools, retrieval, and memory so the model has useful information beyond its immediate prompt.\nExample Suppose a user says:\n1Analyze this experiment. Semantic memory might provide:\n1The team\u0026#39;s primary metric is conversion rate. Episodic memory might provide:\n1Last month\u0026#39;s experiment had a logging problem on Android. Procedural memory might provide:\n1Always check sample ratio mismatch before evaluating experiment results. Together, these memories can help the agent make better decisions.\n9. Context Management This is one of the hardest problems in agent systems.\nLLMs have limited context.\nImagine a long running agent generates:\n1User messages 2 3Agent thoughts 4 5Tool calls 6 7Tool outputs 8 9Search results 10 11Documents 12 13Subagent results 14 15Logs 16 17Plans 18 19Memories After enough work, the context becomes enormous.\nYou cannot simply keep adding everything forever.\nContext Is Like a Desk Imagine you are working at a desk.\nYou need the documents related to your current task.\nIf someone puts 10,000 random documents on your desk, having more information does not necessarily help.\nIt may make your work harder.\nAn agent has a similar problem.\nGood context management means deciding:\n1What should stay? 2 3What should be removed? 4 5What should be summarized? 6 7What should be stored externally? 8 9What should be retrieved later? Anthropic's context engineering guidance recommends keeping the model's working context focused on relevant information and using external persistence or summaries for information that does not need to remain in the immediate context.\nContext Compaction Suppose the current context contains:\n140,000 tokens Much of it describes completed work.\nYou can summarize it.\n140,000 tokens 2 3 ↓ 4 5Summary 6 7 ↓ 8 94,000 tokens The summary may contain:\n1Completed tasks 2 3Important decisions 4 5Important results 6 7Current plan 8 9Remaining tasks Anthropic has described compaction and external memory as ways for long running agents to continue working without keeping every previous token in active context.\nSubagents Can Help With Context Imagine your main agent is researching five companies.\nInstead of putting all research into one context:\n1Main Agent 2 3Company A 4Company B 5Company C 6Company D 7Company E create separate subagents.\n1 Main Agent 2 ↓ 3 ┌────────────┼────────────┐ 4 ↓ ↓ ↓ 5 Agent A Agent B Agent C 6 Company A Company B Company C Each agent gets its own context.\nThe main agent receives summaries.\nThis can reduce context pressure and allow independent tasks to run at the same time.\n10. Guardrails Giving agents tools creates risk.\nImagine an agent can:\n1Send emails 2 3Delete files 4 5Update databases 6 7Deploy code 8 9Issue refunds You do not want the model to perform every action without checks.\nThis is where guardrails become useful.\nOpenAI describes guardrails as checks and validations that can run on agent inputs and outputs.\nInput Guardrails Check what enters the system.\nFor example:\n1User Request 2 ↓ 3Input Guardrail 4 ↓ 5Agent The guardrail might detect:\n1Unsupported request 2 3Malicious input 4 5Sensitive information 6 7Invalid parameters Output Guardrails Check what the agent produces.\n1Agent Output 2 ↓ 3Output Guardrail 4 ↓ 5User You might check:\n1Does the answer contain private information? 2 3Does it follow the required structure? 4 5Did the agent invent data? 6 7Is a required field missing? Tool Guardrails Tool actions deserve special attention.\nFor example:\n1Agent wants to: 2 3DELETE production database The system should not simply execute that action.\nYou can place checks before the tool call.\n1Agent 2 ↓ 3Tool Request 4 ↓ 5Permission Check 6 ↓ 7Approval 8 ↓ 9Execute 11. Human in the Loop Some decisions should involve a person.\nImagine an agent wants to:\n1Send $50,000 2 3Delete a production database 4 5Publish a public statement 6 7Deploy a major production change 8 9Cancel a customer\u0026#39;s subscription Even if the agent is confident, you may want human approval.\nThis is called human in the loop.\nModern agent systems can pause a run before a sensitive tool executes, ask a human to approve or reject the action, save the run state, and continue later. OpenAI's Agents SDK provides this approval and resume pattern.\nExample 1Agent: 2 3I want to issue a $2,000 refund. 4 5 ↓ 6 7System pauses. 8 9 ↓ 10 11Human: 12 13Approve 14or 15Reject 16 17 ↓ 18 19Agent continues. This gives the agent autonomy for normal work while keeping humans involved in important decisions.\nNot Every Action Needs Approval If every action requires approval:\n1Search web 2 3Approve? 4 5Read document 6 7Approve? 8 9Run simple query 10 11Approve? 12 13Generate summary 14 15Approve? The system becomes frustrating.\nInstead, use approval based on risk.\nFor example:\n1Read database 2 ↓ 3No approval 1Modify production database 2 ↓ 3Approval required 1Draft email 2 ↓ 3No approval 1Send email 2 ↓ 3Approval required This is much more practical.\n12. Evaluation This is where many agent projects become difficult.\nA normal software function is easy to test.\n12 + 2 Expected:\n14 An agent can take many different paths and still produce a good result.\nFor example:\n1Task: 2Research a company. Agent A might use five searches.\nAgent B might use eight searches.\nAgent C might use three searches and two database queries.\nAll three may produce correct reports.\nSo evaluating agents requires more than checking one final string.\nAnthropic's agent evaluation guidance emphasizes evaluating both outcomes and agent behavior, because agents may take many turns, call tools, modify state, and adapt based on intermediate results.\nWhat Should You Evaluate? You can evaluate several layers.\nFinal Answer Quality 1Was the answer correct? 2 3Was it complete? 4 5Did it follow the instructions? Tool Selection 1Did the agent choose the right tool? Tool Arguments 1Did the agent send correct parameters? Routing 1Did the request go to the correct specialist? Planning 1Did the agent create a useful plan? Memory 1Did it retrieve the right memories? Safety 1Did it avoid dangerous actions? Efficiency 1How many LLM calls? 2 3How many tool calls? 4 5How many tokens? 6 7How much time? 8 9How much cost? Evaluate the Journey, Not Only the Destination Imagine an agent gives the correct answer.\nBut internally it:\n1Called 30 unnecessary tools 2 3Used 500,000 tokens 4 5Failed six times 6 7Accidentally modified data 8 9Recovered by luck The final answer alone does not tell you whether this is a good agent.\nYou also need to inspect the path it took.\n13. Evaluator Agents You can sometimes use another model to evaluate the result.\nFor example:\n1Research Agent 2 ↓ 3Creates report 4 ↓ 5Evaluator Agent 6 ↓ 7Checks report 8 ↓ 9Feedback 10 ↓ 11Research Agent improves report This creates a generator and evaluator pattern.\nAnthropic has also experimented with multi agent designs where one agent generates work and another evaluates it.\nBut the evaluator is also an LLM.\nIt can make mistakes.\nSo do not assume:\n1LLM evaluated it 2= 3It must be correct Use deterministic tests whenever possible.\nFor example:\n1Schema validation 2 3SQL tests 4 5Unit tests 6 7Required fields 8 9Numeric checks 10 11Permission checks Then use LLM evaluation for things that are difficult to measure with simple rules.\n14. Observability When a multi agent system fails, you need to understand why.\nImagine the user gets a bad answer.\nWhat happened?\nMaybe:\n1Router selected wrong agent 2 3Agent selected wrong tool 4 5Tool returned bad data 6 7Context was missing 8 9Memory retrieval failed 10 11Agent misunderstood tool output 12 13Evaluator approved a bad result Without observability, debugging becomes guessing.\nYou should record useful information such as:\n1Request ID 2 3Agent used 4 5Model used 6 7Prompt version 8 9Tool calls 10 11Tool results 12 13Handoffs 14 15State changes 16 17Memory retrieval 18 19Latency 20 21Token usage 22 23Cost 24 25Errors 26 27Final result Think about the entire workflow as a trace.\n1User Request 2 3 ↓ 200 ms 4 5Router 6 7 ↓ 8 9Research Agent 10 11 ↓ 1.4 sec 12 13Search Tool 14 15 ↓ 16 17Research Agent 18 19 ↓ 20 21Analysis Agent 22 23 ↓ 800 ms 24 25Python Tool 26 27 ↓ 28 29Evaluator 30 31 ↓ 32 33Final Answer Now you can see where time and failures occur.\n15. Failure Handling Agents will fail.\nTools will fail.\nAPIs will time out.\nModels may return invalid output.\nWorkers may crash.\nYour architecture should expect this.\nRetries Suppose an API temporarily fails.\n1Tool call 2 ↓ 3Error 4 ↓ 5Retry But do not retry everything forever.\nUse limits.\n1Attempt 1 2 3Attempt 2 4 5Attempt 3 6 7Stop Be Careful With Actions Imagine:\n1Agent calls: 2 3send_payment($1000) The request succeeds.\nBut the response is lost.\nThe agent thinks it failed and retries.\nNow:\n1Payment 1 = $1000 2 3Payment 2 = $1000 That is a serious problem.\nFor important actions, design tools so repeated calls do not accidentally repeat the action.\nFor example, use a unique operation ID.\n1payment_request_id = abc123 If abc123 was already processed, the system does not process it again.\nSave Progress Long running agents should not restart from zero whenever something crashes.\nImagine:\n1Task has 20 steps 2 318 completed 4 5Server crashes Bad design:\n1Start again at Step 1 Better design:\n1Load saved state 2 3Continue from Step 19 OpenAI documents durable execution integrations for agent runs that may include long waits, retries, restarts, and human approval steps.\n16. Parallel Agents Multiple agents become especially useful when tasks are independent.\nImagine you need research about four companies.\nYou could do:\n1Company A 2 ↓ 3Company B 4 ↓ 5Company C 6 ↓ 7Company D That is sequential.\nOr:\n1 Orchestrator 2 ↓ 3 ┌──────────┼──────────┐ 4 ↓ ↓ ↓ 5 Company A Company B Company C 6 ↓ 7 Company D Several research tasks can happen at the same time.\nAnthropic has described parallel subagents as one of the benefits of multi agent architectures, particularly when separate parts of a task can be investigated independently.\nBut Parallelism Has a Cost If ten agents run simultaneously, you may also create:\n110 model requests 2 3More tokens 4 5More API calls 6 7More database traffic 8 9More memory usage 10 11More cost Use parallel agents where parallel work actually makes sense.\n17. Context Boundaries Between Agents One useful design principle is:\nEach agent should receive only the context it needs.\nImagine a software engineering system.\nYou have:\n1Frontend Agent 2 3Backend Agent 4 5Database Agent 6 7Security Agent The frontend agent may need:\n1UI requirements 2 3Design system 4 5Frontend code 6 7API specification It probably does not need:\n1Every database migration 2 3Every security log 4 5Every backend test result Smaller focused contexts can make specialized agents easier to control.\n18. Give Agents Clear Responsibilities Bad agent design:\n1Agent 1: 2Help with stuff. 3 4Agent 2: 5Also help with stuff. 6 7Agent 3: 8Do whatever is needed. Now the agents overlap.\nRouting becomes difficult.\nGood design:\n1Research Agent 2 3Responsibility: 4Collect and verify information. 5 6Tools: 7Web search 8Document search 1Data Agent 2 3Responsibility: 4Analyze structured data. 5 6Tools: 7SQL 8Python 9BigQuery 1Writer Agent 2 3Responsibility: 4Turn verified findings into a clear report. 5 6Tools: 7Document editor Clear boundaries make the system easier to understand and evaluate.\n19. A Full Example Let us build a multi agent system for business analysis.\nThe user asks:\n1Analyze why our revenue decreased this month 2and prepare a report for leadership. We have these agents:\n1Orchestrator 2 3Analytics Agent 4 5Experiment Agent 6 7Research Agent 8 9Writer Agent 10 11Reviewer Agent Step 1: Request Arrives 1User 2 ↓ 3Analyze why revenue decreased. Step 2: Orchestrator Understands the Goal The orchestrator creates a plan.\n11. Check revenue metrics. 2 32. Find which segments declined. 4 53. Check recent product experiments. 6 74. Check known incidents. 8 95. Identify likely causes. 10 116. Write report. 12 137. Review claims. Step 3: Router Sends Work 1 Orchestrator 2 ↓ 3 ┌───────────────┼───────────────┐ 4 ↓ ↓ ↓ 5 Analytics Agent Experiment Agent Research Agent Step 4: Agents Use Tools Analytics Agent:\n1BigQuery 2 3Python 4 5Metrics API Experiment Agent:\n1Experiment database 2 3Feature flag system Research Agent:\n1Incident logs 2 3Internal documents Step 5: Agents Update Shared State 1Analytics: 2 3Revenue decreased 12%. 4 5Largest decline came from mobile users in Germany. 1Experiments: 2 3New checkout experiment launched seven days ago. 1Research: 2 3Payment failures increased after a payment provider change. Step 6: Orchestrator Combines Results Now the orchestrator sees:\n1Revenue decline 2 3Mobile Germany decline 4 5Checkout experiment 6 7Payment failures It may ask the Analytics Agent:\n1Compare payment failure rate before and after 2the checkout experiment. This is important.\nAgent systems are not always one straight line.\nThey can create new tasks after discovering new information.\nStep 7: Writer Creates the Report The Writer Agent receives only the useful findings.\n1Verified metrics 2 3Important events 4 5Likely causes 6 7Supporting evidence Then creates the report.\nStep 8: Reviewer Checks It 1Report 2 ↓ 3Reviewer Agent 4 ↓ 5Check claims 6 ↓ 7Check numbers 8 ↓ 9Check unsupported conclusions Step 9: Human Reviews Important Conclusions Before the report goes to leadership:\n1AI Report 2 ↓ 3Human Review 4 ↓ 5Approved 6 ↓ 7Leadership Now we have a full multi agent workflow.\nThe Architecture The final architecture could look like:\n1 User 2 ↓ 3 API 4 ↓ 5 Harness 6 ↓ 7 Orchestrator 8 ↓ 9 Router 10 ↓ 11 ┌─────────────────┼─────────────────┐ 12 ↓ ↓ ↓ 13 Analytics Research Experiment 14 Agent Agent Agent 15 ↓ ↓ ↓ 16 Tools Tools Tools 17 ↓ ↓ ↓ 18 └─────────────────┼─────────────────┘ 19 ↓ 20 Shared State 21 ↓ 22 Memory 23 ↓ 24 Writer Agent 25 ↓ 26 Reviewer Agent 27 ↓ 28 Guardrails 29 ↓ 30 Human Approval 31 ↓ 32 Final Output Around everything, we also need:\n1Logging 2 3Tracing 4 5Evaluation 6 7Retries 8 9Permissions 10 11Cost Monitoring A Simple Mental Model If this article feels like a lot of information, remember this analogy.\nImagine a company.\nThe agents are employees.\n1Researcher 2 3Engineer 4 5Analyst 6 7Writer The orchestrator is the manager.\n1Who should work on what? The router is the receptionist.\n1Who should receive this request? The tools are the software employees use.\n1Database 2 3Browser 4 5Python 6 7Email The state is the project board.\n1What are we currently doing? The memory is the company's knowledge.\n1What have we learned before? The context is the information currently sitting on an employee's desk.\n1What does this person need right now? The guardrails are company rules.\n1What are employees allowed to do? The human approval system is management authorization.\n1Does a person need to approve this action? The evaluation system is quality control.\n1Did we do the work correctly? The observability system is the activity log.\n1What happened and why? The harness is the operating environment that connects all these pieces.\nWhat Makes a Good Multi Agent System? A good multi agent system is not the system with the most agents.\nIt is the system where responsibilities are clear.\nYou want something like:\n1Clear Agents 2 3 ↓ 4 5Clear Responsibilities 6 7 ↓ 8 9Clear Tool Access 10 11 ↓ 12 13Good Routing 14 15 ↓ 16 17Controlled Context 18 19 ↓ 20 21Reliable State 22 23 ↓ 24 25Useful Memory 26 27 ↓ 28 29Safe Actions 30 31 ↓ 32 33Strong Evaluation 34 35 ↓ 36 37Observable Behavior Common Mistake 1: Creating Too Many Agents It is easy to create:\n1Planning Agent 2 3Thinking Agent 4 5Research Agent 6 7Search Agent 8 9Web Agent 10 11Review Agent 12 13Quality Agent 14 15Supervisor Agent 16 17Manager Agent 18 19Manager of Managers Agent The architecture looks impressive.\nBut it may perform worse.\nEvery extra agent creates another point where information can be lost or misunderstood.\nStart small.\nFor example:\n1Orchestrator 2 3Specialist A 4 5Specialist B Add another agent only when you can explain exactly why it is needed.\nCommon Mistake 2: Letting Every Agent See Everything More context does not always mean better performance.\nDo not send:\n1Entire conversation 2 3Entire database schema 4 5All available tools 6 7Every previous agent message 8 9All memories to every agent.\nRetrieve what is relevant.\nCommon Mistake 3: Giving Agents Too Much Power Do not give every agent:\n1Database delete access 2 3Production deployment access 4 5Email sending access 6 7Payment access Use the minimum permissions each agent needs.\nCommon Mistake 4: No Evaluation A demo working three times does not mean the system is ready.\nBuild an evaluation set.\nFor example:\n1100 routing examples 2 3100 tool selection examples 4 550 failure scenarios 6 750 memory retrieval examples 8 9100 end to end tasks Run them whenever you change:\n1Model 2 3Prompt 4 5Tools 6 7Router 8 9Memory 10 11Agent architecture Common Mistake 5: No Trace of What Happened If the final response is wrong and all you saved is:\n1Final answer debugging will be painful.\nYou want to know:\n1Which agent ran? 2 3Why was it selected? 4 5Which tools were called? 6 7What did the tools return? 8 9Which memories were used? 10 11What state changed? 12 13Where did the failure begin? When Should You Use Multi Agent Systems? Multi agent systems make the most sense when the problem naturally contains separate areas of expertise or independent work.\nFor example:\n1Deep research 2 3Software development 4 5Business analysis 6 7Data science 8 9Customer support 10 11Security investigation 12 13Complex operations 14 15Long running workflows They can also help when separate tasks can run in parallel or when separate contexts prevent one agent from becoming overloaded.\nBut if your task is:\n1Question 2 ↓ 3Search 4 ↓ 5Answer keep it simple.\nFinal Takeaway Building a multi agent system is not mainly about connecting several LLMs.\nThe difficult part is building the system around the LLMs.\nYou need to answer questions such as:\n1Who decides what happens next? 2 3Which agent owns each task? 4 5How do agents communicate? 6 7Where is task state stored? 8 9Which tools can each agent use? 10 11What should the system remember? 12 13What context should each agent receive? 14 15How should the system create and update plans? 16 17How should tasks be routed? 18 19Which actions need protection? 20 21When should a human approve something? 22 23How do we measure whether the agents are good? 24 25How do we understand failures? That is why the harness matters so much.\nThe LLM provides intelligence.\nThe harness provides structure.\nA simple way to remember the whole system is:\n1 User 2 ↓ 3 Harness 4 ↓ 5 Orchestrator 6 ↓ 7 Router 8 ↓ 9 ┌────────────┼────────────┐ 10 ↓ ↓ ↓ 11 Agent A Agent B Agent C 12 ↓ ↓ ↓ 13 Tools Tools Tools 14 ↓ ↓ ↓ 15 └────────────┼────────────┘ 16 ↓ 17 State 18 ↓ 19 Memory 20 ↓ 21 Review 22 ↓ 23 Guardrails 24 ↓ 25 Human Approval 26 ↓ 27 Output And surrounding the whole system:\n1Context Management 2 3Evaluation 4 5Tracing 6 7Permissions 8 9Retries 10 11Cost Control The goal is not to create as many agents as possible.\nThe goal is to create the smallest group of agents that can work together reliably to solve a problem that one agent cannot solve well enough on its own.\nThat is the foundation of a strong multi agent AI system.\n","link":"https://blog.sksoumik.com/artificial-intelligence/building-multi-agent-ai-systems/","section":"artificial-intelligence","tags":["artificial intelligence","agentic ai","system design"],"title":"Building Multi Agent AI Systems: From Orchestration to Production"},{"body":"","link":"https://blog.sksoumik.com/tags/ai-tools/","section":"tags","tags":null,"title":"ai-tools"},{"body":"","link":"https://blog.sksoumik.com/series/ai-tools/","section":"series","tags":null,"title":"ai-tools"},{"body":"","link":"https://blog.sksoumik.com/tags/developer-tools/","section":"tags","tags":null,"title":"developer-tools"},{"body":"","link":"https://blog.sksoumik.com/categories/machine-learning/","section":"categories","tags":null,"title":"machine learning"},{"body":"","link":"https://blog.sksoumik.com/tags/mcp/","section":"tags","tags":null,"title":"mcp"},{"body":" Author: Sadman Kabir Soumik\nWhat is MCP? MCP stands for Model Context Protocol. It is an open standard created by Anthropic in November 2024. Think of it as a universal plug that connects AI tools (like Claude Code or Cursor) to external data sources and services.\nBefore MCP, if you wanted your AI coding assistant to access your database, you needed to build a custom connection. If you also wanted it to access GitHub, you needed another custom connection. And for Slack? Yet another one. This was a lot of work.\nMCP solves this problem. It provides one standard way to connect AI tools to anything - databases, APIs, file systems, cloud services, and more.\nWhy Should You Care About MCP? Here is a simple example. Let's say you are using Claude Code to build a web app. Without MCP, you have to:\nCopy data from your database Paste it into your chat Ask Claude to help you Copy the response back With MCP, Claude Code can directly talk to your database. You just ask \u0026quot;What are the top 10 customers by order value?\u0026quot; and it queries your database for you.\nMCP makes your AI assistant smarter because it can see and interact with your actual tools and data.\nHow Does MCP Work? The setup is simple. There are three parts:\nMCP Client - This is your AI tool (Claude Code, Cursor, GitHub Copilot) MCP Server - This is a small program that connects to a specific service (like GitHub, Postgres, or Slack) Transport - This is how they communicate (usually through stdio or HTTP) Here is how they work together:\n1Your AI Tool (Client) \u0026lt;---\u0026gt; MCP Server \u0026lt;---\u0026gt; External Service 2 (Claude Code) (GitHub MCP) (GitHub API) The MCP server acts as a bridge. It knows how to talk to the external service and presents that data in a way your AI tool can understand.\nPopular MCP Servers There are many ready-to-use MCP servers. Here are some popular ones:\nServer What It Does GitHub Read issues, manage PRs, analyze code Postgres Query your database Filesystem Read and write files in allowed folders Slack Send messages, read channels Memory Store and recall information Playwright Automate browser tasks Git Work with git repositories You can find more servers at the official MCP servers repository.\nSetting Up MCP with Claude Code Claude Code has built-in support for MCP. Here is how to add servers:\nAdding a Server with the CLI The basic command is:\n1claude mcp add \u0026lt;server-name\u0026gt; -- \u0026lt;command\u0026gt; Example 1: Add the Filesystem Server This lets Claude Code read and write files in specific folders:\n1claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem ~/Documents ~/Projects Now Claude Code can access files in your Documents and Projects folders.\nExample 2: Add the GitHub Server First, create a GitHub personal access token. Then run:\n1claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN=your_token_here -- npx -y @modelcontextprotocol/server-github Now you can ask Claude Code things like:\n\u0026quot;List my open GitHub issues\u0026quot; \u0026quot;Create a PR for the current branch\u0026quot; \u0026quot;What commits were made this week?\u0026quot; Example 3: Add a Postgres Database 1claude mcp add postgres -e DATABASE_URL=\u0026#34;postgresql://user:pass@localhost:5432/mydb\u0026#34; -- npx -y @modelcontextprotocol/server-postgres Now Claude Code can query your database directly.\nManaging Your Servers Use these commands to manage your MCP servers:\n1# List all servers 2claude mcp list 3 4# Get details about a server 5claude mcp get github 6 7# Remove a server 8claude mcp remove github 9 10# Check server status inside Claude Code 11/mcp Understanding Scope When you add a server, you can choose where to save the config:\nlocal (default): Only you can use it, only in this project project: Everyone on the project can use it (saved in .mcp.json) user: You can use it in all your projects 1# Add server for all your projects 2claude mcp add filesystem -s user -- npx -y @modelcontextprotocol/server-filesystem ~/Documents 3 4# Add server for everyone on the team 5claude mcp add github -s project -e GITHUB_PERSONAL_ACCESS_TOKEN=token -- npx -y @modelcontextprotocol/server-github Setting Up MCP with Cursor Cursor also supports MCP servers. The setup is different - you use a JSON config file.\nStep 1: Open MCP Settings Go to File \u0026gt; Preferences \u0026gt; Cursor Settings and select MCP.\nStep 2: Create or Edit the Config File The config file is at ~/.cursor/mcp.json. Here is an example:\n1{ 2 \u0026#34;mcpServers\u0026#34;: { 3 \u0026#34;filesystem\u0026#34;: { 4 \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, 5 \u0026#34;args\u0026#34;: [\u0026#34;-y\u0026#34;, \u0026#34;@modelcontextprotocol/server-filesystem\u0026#34;, \u0026#34;/Users/you/Projects\u0026#34;] 6 }, 7 \u0026#34;github\u0026#34;: { 8 \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, 9 \u0026#34;args\u0026#34;: [\u0026#34;-y\u0026#34;, \u0026#34;@modelcontextprotocol/server-github\u0026#34;], 10 \u0026#34;env\u0026#34;: { 11 \u0026#34;GITHUB_PERSONAL_ACCESS_TOKEN\u0026#34;: \u0026#34;your_token_here\u0026#34; 12 } 13 }, 14 \u0026#34;postgres\u0026#34;: { 15 \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, 16 \u0026#34;args\u0026#34;: [\u0026#34;-y\u0026#34;, \u0026#34;@modelcontextprotocol/server-postgres\u0026#34;], 17 \u0026#34;env\u0026#34;: { 18 \u0026#34;DATABASE_URL\u0026#34;: \u0026#34;postgresql://user:pass@localhost:5432/mydb\u0026#34; 19 } 20 } 21 } 22} Step 3: Start the Servers After saving the config, click the \u0026quot;Start\u0026quot; button in Cursor to start your MCP servers.\nUsing Docker for Servers Some servers work better with Docker. Here is an example for the GitHub server:\n1{ 2 \u0026#34;mcpServers\u0026#34;: { 3 \u0026#34;github\u0026#34;: { 4 \u0026#34;command\u0026#34;: \u0026#34;docker\u0026#34;, 5 \u0026#34;args\u0026#34;: [ 6 \u0026#34;run\u0026#34;, \u0026#34;-i\u0026#34;, \u0026#34;--rm\u0026#34;, 7 \u0026#34;-e\u0026#34;, \u0026#34;GITHUB_PERSONAL_ACCESS_TOKEN\u0026#34;, 8 \u0026#34;ghcr.io/github/github-mcp-server\u0026#34; 9 ], 10 \u0026#34;env\u0026#34;: { 11 \u0026#34;GITHUB_PERSONAL_ACCESS_TOKEN\u0026#34;: \u0026#34;your_token_here\u0026#34; 12 } 13 } 14 } 15} Setting Up MCP with GitHub Copilot GitHub Copilot supports MCP in VS Code and GitHub.com.\nIn VS Code You need VS Code version 1.99 or later.\nStep 1: Create a .vscode/mcp.json file in your project:\n1{ 2 \u0026#34;mcp\u0026#34;: { 3 \u0026#34;servers\u0026#34;: { 4 \u0026#34;filesystem\u0026#34;: { 5 \u0026#34;command\u0026#34;: \u0026#34;npx\u0026#34;, 6 \u0026#34;args\u0026#34;: [\u0026#34;-y\u0026#34;, \u0026#34;@modelcontextprotocol/server-filesystem\u0026#34;, \u0026#34;./src\u0026#34;] 7 } 8 } 9 } 10} Step 2: Open the file and click \u0026quot;Start\u0026quot; to start the servers.\nStep 3: Use the tools by typing # followed by the tool name, or just ask naturally like \u0026quot;List my files\u0026quot;.\nUsing the GitHub MCP Registry VS Code has a built-in registry of MCP servers. You can browse and install them without writing any config.\nOpen the Command Palette Search for \u0026quot;MCP\u0026quot; Select \u0026quot;Add MCP Server from Registry\u0026quot; Choose the server you want For GitHub Copilot Coding Agent If you use GitHub's coding agent, you can add MCP servers in your repository settings on GitHub.com. The agent automatically has access to:\nGitHub MCP - For issues and PRs Playwright MCP - For browser automation Real World Examples Here are some practical ways to use MCP:\nExample 1: Database-Aware Coding With Postgres MCP connected:\n1You: \u0026#34;Generate a Python function to get the top 10 customers by total orders\u0026#34; 2 3Claude: *queries your actual database schema* 4 *sees your customers and orders tables* 5 *writes code that matches your real column names* Example 2: Issue-Driven Development With GitHub MCP connected:\n1You: \u0026#34;Implement the feature described in issue #42\u0026#34; 2 3Claude: *reads issue #42 from GitHub* 4 *understands the requirements* 5 *writes the code* 6 *can even create a PR when done* Example 3: File-Aware Refactoring With Filesystem MCP connected:\n1You: \u0026#34;Find all files using the old API and update them to the new one\u0026#34; 2 3Claude: *searches your project files* 4 *finds all files with old API calls* 5 *updates each one* Security Tips MCP servers can access sensitive data. Here are some tips to stay safe:\nOnly install servers you trust - Check the source and reviews Use minimal permissions - Give servers access only to what they need Be careful with credentials - Use environment variables, not plain text in configs Review before running - Check what a server does before installing it Use read-only mode when possible - Many servers have read-only options For database servers, you can often set access modes:\n1# Read-only access 2claude mcp add postgres -e DATABASE_URL=\u0026#34;...\u0026#34; -- npx server-postgres --read-only 3 4# Full access (be careful) 5claude mcp add postgres -e DATABASE_URL=\u0026#34;...\u0026#34; -- npx server-postgres --access-mode=unrestricted Troubleshooting Server Not Starting Check if the server is installed:\n1npx -y @modelcontextprotocol/server-github --version Timeout Errors Increase the timeout:\n1MCP_TIMEOUT=10000 claude # 10 second timeout Server Output Too Long Increase the output limit:\n1MAX_MCP_OUTPUT_TOKENS=50000 claude Windows Issues On Windows (not WSL), wrap npx with cmd:\n1claude mcp add my-server -- cmd /c npx -y @some/package The Future of MCP MCP is growing fast. Major companies like OpenAI and Google have adopted it. As of late 2025, MCP includes:\nTasks - Track long-running operations OAuth support - Secure authentication Payment processing - Handle transactions (for apps that need it) More and more services are building MCP servers. This means your AI assistant will keep getting more powerful as new integrations become available.\nConclusion MCP servers are like superpowers for your AI coding tools. They let Claude Code, Cursor, and GitHub Copilot connect to your databases, APIs, and services directly.\nThe setup is simple:\nChoose the MCP servers you need Add them with a simple command or JSON config Start asking your AI tool to use them Try starting with the GitHub or Filesystem server. Once you see how useful it is to have your AI tool connected to your actual data, you will want to add more.\nThe future of AI coding is not just about smarter models - it is about smarter connections. MCP makes those connections easy.\nReferences:\n[1] https://modelcontextprotocol.io\n[2] https://github.com/modelcontextprotocol/servers\n[3] https://code.claude.com/docs/en/mcp\n[4] https://cursor.directory/mcp\n[5] https://docs.github.com/copilot/customizing-copilot/using-model-context-protocol\n","link":"https://blog.sksoumik.com/artificial-intelligence/mcp-servers-guide-ai-tools-integration/","section":"artificial-intelligence","tags":["artificial-intelligence","mcp","ai-tools","developer-tools"],"title":"MCP Servers Explained - Connect Your AI Tools to Everything"},{"body":"","link":"https://blog.sksoumik.com/series/","section":"series","tags":null,"title":"Series"},{"body":"","link":"https://blog.sksoumik.com/tags/fine-tuning/","section":"tags","tags":null,"title":"fine tuning"},{"body":"Training a large language model usually happens in more than one stage.\nThe first stage is pretraining.\nThis is where the model learns language, facts, patterns, coding concepts, reasoning patterns, and many other things from a very large amount of data.\nBut a pretrained model is not automatically a good assistant.\nIt might know a lot, but it may still struggle to follow instructions, answer in the right format, understand human preferences, or behave the way we want.\nThat is where post training comes in.\nPost training takes a pretrained model and teaches it how we actually want it to behave.\nSome of the most common post training techniques are:\nSupervised fine tuning Preference training Reinforcement learning Knowledge distillation In this blog, we will understand what each technique does, why we need it, and how they fit together.\n1. Pretraining vs Post Training Before looking at individual techniques, we first need to understand the difference between pretraining and post training.\nPretraining During pretraining, an LLM learns by reading a huge amount of text.\nFor example, imagine the model sees a sentence like:\n1The capital of France is Paris. Later, it may see:\n1Paris is the largest city in France. And millions of other examples involving countries, cities, history, programming, science, conversations, and many other topics.\nThe model learns by repeatedly trying to predict what comes next.\nA simplified example would be:\n1The capital of France is _____ The model tries to predict:\n1Paris It does this billions or even trillions of times across a massive dataset.\nOver time, the model learns patterns in language and information.\nThe result is called a base model.\nConceptually:\n1Huge amount of text data 2 3 ↓ 4 5 Pretraining 6 7 ↓ 8 9 Base LLM The base model may know a lot, but knowledge alone does not make it a good assistant.\nPost Training Post training happens after pretraining.\nInstead of teaching the model general language knowledge, we now focus on improving its behavior.\nFor example, we may want the model to:\n1Follow instructions 2 3Give useful answers 4 5Write in a certain style 6 7Solve reasoning problems 8 9Avoid unwanted responses 10 11Use tools correctly 12 13Follow human preferences So the overall process looks like:\n1Large training dataset 2 3 ↓ 4 5 Pretraining 6 7 ↓ 8 9 Base LLM 10 11 ↓ 12 13 Post Training 14 15 ↓ 16 17 Assistant Model Chat based models that people interact with are usually the result of both stages.\nA simple way to remember the difference is:\nPretraining teaches the model what it knows.\nPost training teaches the model how we want it to use what it knows.\nNow let us look at the main post training techniques.\n2. Supervised Fine Tuning Supervised fine tuning, often called SFT, is probably the easiest post training technique to understand.\nThe idea is simple.\nWe give the model examples of good answers and train it to produce similar answers.\nSuppose our base model receives this question:\n1User: 2Explain photosynthesis in simple language. We prepare a high quality answer:\n1Assistant: 2Photosynthesis is the process plants use to make food. 3 4Plants take sunlight, water, and carbon dioxide 5and use them to produce energy in the form of sugar. Our training dataset contains many examples like this:\n1Instruction → Good response 2Instruction → Good response 3Instruction → Good response 4Instruction → Good response Then we train the base model on those examples.\nConceptually:\n1Base Model 2 3 + 4 5High Quality Examples 6 7 ↓ 8 9Supervised Fine Tuning 10 11 ↓ 12 13Instruction Following Model The model starts learning what a good answer should look like.\nWhat Can SFT Teach? SFT can teach the model many useful behaviors.\nFor example:\n1How to answer questions 2 3How to follow instructions 4 5How to generate JSON 6 7How to write code 8 9How to summarize documents 10 11How to use a particular tone 12 13How to solve domain specific tasks Imagine that we are building an LLM for customer support.\nWe could prepare examples such as:\n1Customer question: 2How can I reset my password? 3 4Good answer: 5Go to the login page and select \u0026#34;Forgot password.\u0026#34; 6Then follow the instructions sent to your email. After seeing thousands of examples like this, the model becomes much better at answering customer support questions.\nWhere Does The Training Data Come From? SFT data can come from several places.\nFor example:\n1Human written answers 2 3Existing company datasets 4 5Expert generated answers 6 7Synthetic answers generated by another LLM 8 9Carefully cleaned public datasets The quality of this data matters a lot.\nIf the training examples are poor, the model will learn poor behavior.\nThis gives us an important rule:\nGood fine tuning starts with good training data.\n3. Preference Training SFT teaches the model what a good answer looks like.\nBut there is another problem.\nSometimes there are several reasonable answers to the same question.\nConsider this prompt:\n1Explain machine learning to someone with no technical background. Imagine the model produces two answers.\nResponse A 1Machine learning uses statistical optimization methods 2to estimate functions from observed data distributions. Response B 1Machine learning is a way of teaching computers 2to learn patterns from examples instead of writing 3every rule manually. Both responses might contain correct information.\nBut most people would probably prefer Response B.\nHow do we teach the model that?\nThis is where preference training comes in.\nInstead of simply saying:\n1This is the correct answer. we provide information like:\n1For this question: 2 3Response B is better than Response A. The model learns which kinds of answers humans prefer.\nConceptually:\n1Prompt 2 3 ↓ 4 5Multiple Responses 6 7 ↓ 8 9Which Response Is Better? 10 11 ↓ 12 13Preference Training 14 15 ↓ 16 17Model Learns Human Preferences What Kind Of Preferences Can We Teach? Preference data can represent many things.\nFor example:\n1Clear answer \u0026gt; confusing answer 2 3Correct answer \u0026gt; incorrect answer 4 5Concise answer \u0026gt; unnecessarily long answer 6 7Helpful answer \u0026gt; unhelpful answer 8 9Instruction following \u0026gt; ignoring instructions The interesting part is that preference training does not always tell the model exactly what to write.\nInstead, it teaches the model to understand which response is better.\nWhere Do Preferences Come From? Traditionally, humans compare model responses.\nFor example, an evaluator sees:\n1Prompt 2 3Response A 4 5Response B Then selects the better response.\nBut using humans for millions of comparisons can be expensive.\nToday, stronger LLMs are also commonly used to judge responses and generate preference datasets.\nThis is sometimes called AI feedback.\n4. Reinforcement Learning Preference training tells us which outputs are better.\nReinforcement learning takes this idea further.\nInstead of only showing the model examples, we allow the model to generate answers and then give it a reward based on how good those answers are.\nThink about training a dog.\nWhen the dog performs the correct action, it receives a reward.\nOver time, the dog learns which actions lead to rewards.\nThe basic idea in reinforcement learning is similar.\nFor an LLM:\n1Prompt 2 3 ↓ 4 5Model Generates Response 6 7 ↓ 8 9Response Is Evaluated 10 11 ↓ 12 13Reward 14 15 ↓ 16 17Model Updates Its Behavior If an answer receives a high reward, the training process encourages similar behavior.\nIf the answer receives a low reward, the model learns to avoid that behavior.\nRLHF One of the best known approaches is Reinforcement Learning from Human Feedback, commonly called RLHF.\nA simplified RLHF pipeline looks like this:\n1Humans compare responses 2 3 ↓ 4 5Build preference data 6 7 ↓ 8 9Train a reward model 10 11 ↓ 12 13LLM generates responses 14 15 ↓ 16 17Reward model scores responses 18 19 ↓ 20 21Reinforcement learning 22 23 ↓ 24 25Improved LLM The reward model tries to predict what humans would prefer.\nThe LLM then learns to generate responses that receive better reward scores.\nReinforcement Learning For Reasoning Reinforcement learning has also become important for reasoning models.\nImagine that we give the model a math problem.\n115 × 12 = ? The model generates a solution.\nIf the final answer is correct, we can reward it.\nIf the answer is incorrect, it gets a lower reward.\nThis works particularly well for tasks where answers can be checked automatically.\nExamples include:\n1Mathematics 2 3Coding 4 5Logic problems 6 7Games 8 9Structured reasoning For programming tasks, for example, we can run the generated code against test cases.\nIf all tests pass, the model receives a strong positive signal.\nThis reduces the need for a human to manually judge every answer.\n5. Knowledge Distillation Now we come to another powerful post training technique.\nKnowledge distillation means using a stronger model to teach a smaller model.\nSuppose we have:\n1Teacher Model 2 370 billion parameters 4 5Very capable 6 7Expensive and:\n1Student Model 2 37 billion parameters 4 5Less capable 6 7Much cheaper We ask the teacher model to solve a large number of tasks.\nFor example:\n1Prompt 2 3↓ 4 5Teacher Model 6 7↓ 8 9High Quality Answer We collect these answers and use them to train the student.\nConceptually:\n1Prompts 2 3 ↓ 4 5Large Teacher Model 6 7 ↓ 8 9Teacher Responses 10 11 ↓ 12 13Training Dataset 14 15 ↓ 16 17Small Student Model The student learns patterns from the teacher's responses.\nWhat Can We Transfer? The teacher can help transfer many kinds of behavior.\nFor example:\n1Instruction following 2 3Writing style 4 5Domain knowledge 6 7Coding behavior 8 9Reasoning patterns 10 11Answer structure 12 13Tool usage Suppose the teacher solves a reasoning problem like this:\n1Question: 2If five machines make 100 products in four hours, 3how many products can ten machines make in four hours? 4 5Teacher: 6Ten machines means twice as many machines. 7 8If the time stays the same, production should double. 9 10100 × 2 = 200. 11 12Answer: 200 products. We can train the smaller model using examples like this.\nAfter seeing many strong examples, the student may become much better at solving similar problems.\nWhy Use Distillation? The main reason is often cost.\nImagine that using the teacher model costs:\n1$10 for a certain amount of traffic while the smaller student costs:\n1$1 for the same amount of traffic If the student becomes good enough for the application, production costs can drop significantly.\nThis makes distillation especially useful when we have a strong but expensive model.\nThe teacher performs expensive work during training.\nThe student performs cheaper work during production.\nThe Student Is Not An Exact Copy There is an important limitation.\nKnowledge distillation does not magically turn a small model into the large model.\nA 7 billion parameter model still has much less capacity than a 70 billion parameter model.\nSome behaviors may transfer very well.\nOthers may not.\nThe goal is usually not:\n1Student = Teacher A more realistic goal is:\n1Student becomes good enough 2for the tasks we actually need. This can still be extremely useful.\n6. How These Techniques Work Together These techniques should not always be thought of as competitors.\nIn real LLM training pipelines, several techniques can be used together.\nFor example:\n1 Pretraining 2 ↓ 3 Base Model 4 ↓ 5 Supervised Fine Tuning 6 ↓ 7 Instruction Following Model 8 ↓ 9 Preference Training 10 ↓ 11 Better Aligned Model 12 ↓ 13 Reinforcement Learning 14 ↓ 15 Better Reasoning Model Knowledge distillation can appear at different stages.\nFor example, after building a very strong model:\n1Strong Model 2 3 ↓ 4 5Generate High Quality Data 6 7 ↓ 8 9Knowledge Distillation 10 11 ↓ 12 13Smaller Model So a real training process could look something like:\n1 Raw Data 2 ↓ 3 Pretraining 4 ↓ 5 Base Model 6 ↓ 7 Supervised Fine Tuning 8 ↓ 9 Preference Training 10 ↓ 11 Reinforcement Learning 12 ↓ 13 Strong Model 14 ↓ 15 Knowledge Distillation 16 ↓ 17 Smaller Production Model This is only one possible pipeline.\nDifferent companies and research teams use different combinations depending on what they are trying to build.\n7. How Should You Choose A Technique? A simple way to think about the techniques is based on the problem you are trying to solve.\nProblem Technique The model does not follow instructions well Supervised fine tuning The model gives correct answers but users prefer a different style Preference training The model needs to learn through rewards and outcomes Reinforcement learning A strong model is too expensive for production Knowledge distillation These techniques can also solve overlapping problems.\nFor example, reasoning ability may be improved using SFT, reinforcement learning, distillation, or a combination of them.\nThe important question is not:\nWhich technique is the best?\nA better question is:\nWhat behavior are we trying to improve, and what training signal can teach that behavior?\n8. A Simple Example Imagine that we want to build a small coding assistant.\nWe start with a pretrained model.\nStage 1: Supervised Fine Tuning We train it on high quality examples:\n1Coding question → Good solution Now the model becomes better at following coding instructions.\nStage 2: Preference Training We compare different generated solutions.\nFor example:\n1Solution A: 2Works but is difficult to understand. 3 4Solution B: 5Works and is simple and readable. We teach the model that Solution B is preferred.\nStage 3: Reinforcement Learning Now we allow the model to generate code.\nWe run automated tests.\n1All tests pass → high reward 2 3Some tests pass → medium reward 4 5Tests fail → low reward The model learns to write code that performs better on actual tests.\nStage 4: Knowledge Distillation Suppose our final model is very powerful but expensive.\nWe ask it to solve millions of coding tasks.\nThen we use those answers to train a smaller model.\nNow we have:\n1Powerful Teacher 2 3 ↓ 4 5Millions of Coding Examples 6 7 ↓ 8 9Smaller Student The smaller model may preserve much of the useful coding ability while being cheaper to run.\nThis example shows how different post training techniques can work together rather than separately.\n9. The Bigger Picture A useful way to understand modern LLM development is to separate knowledge acquisition from behavior improvement.\nPretraining gives the model a broad foundation.\n1Learn language 2 3Learn facts 4 5Learn patterns 6 7Learn basic reasoning Post training then shapes how the model uses that foundation.\n1Follow instructions 2 3Understand preferences 4 5Improve reasoning 6 7Learn specialized tasks 8 9Become cheaper through distillation This is why simply increasing the amount of pretraining data is not enough.\nA model can know a huge amount of information and still be a poor assistant.\nPost training turns that knowledge into useful behavior.\nFinal Thoughts Modern LLMs are not created through one giant training process.\nThey go through multiple stages.\nThe simplest way to remember the process is:\n1Pretraining 2 ↓ 3Teach the model what the world looks like 4 5Post Training 6 ↓ 7Teach the model how we want it to behave And within post training, four important techniques are:\nSupervised fine tuning teaches the model using examples of good answers.\nPreference training teaches the model which answers are better.\nReinforcement learning teaches the model through rewards.\nKnowledge distillation allows a stronger model to teach a smaller model.\nOnce you understand these four ideas, many modern LLM training approaches become much easier to understand.\nThe implementation details can become complicated, but the core ideas are surprisingly simple.\nAt the end of the day, post training is mainly about one question:\nWhat signal should we give the model so that it learns the behavior we want?\n","link":"https://blog.sksoumik.com/artificial-intelligence/how-llm-post-training-works/","section":"artificial-intelligence","tags":["artificial intelligence","machine learning","fine tuning","reinforcement learning"],"title":"How LLM Post Training Works: SFT, Preference Training, Reinforcement Learning, and Distillation"},{"body":"","link":"https://blog.sksoumik.com/tags/machine-learning/","section":"tags","tags":null,"title":"machine learning"},{"body":"","link":"https://blog.sksoumik.com/tags/reinforcement-learning/","section":"tags","tags":null,"title":"reinforcement learning"},{"body":"","link":"https://blog.sksoumik.com/tags/data-science/","section":"tags","tags":null,"title":"data science"},{"body":"","link":"https://blog.sksoumik.com/series/ml/","section":"series","tags":null,"title":"ML"},{"body":"","link":"https://blog.sksoumik.com/tags/nlp/","section":"tags","tags":null,"title":"NLP"},{"body":"Let's first break it down: what exactly are large language models (LLMs), why do we call them 'large,' and how are they different from other types of language models?\nAn LLM is a machine learning model trained on massive amounts of text using transformer-based architectures (or their variations). These models can generate, understand, and process human-like text, making them useful for tasks like translation, summarization, reasoning, coding etc,.\nHow is an LLM different from other language models? Scale: Traditional language models (e.g., n-gram models, early RNNs) were trained on much smaller datasets with far fewer parameters. LLMs, on the other hand, have billions or even trillions of parameters.\nGeneralization: Older models were usually task-specific, while LLMs are general-purpose, capable of handling a wide range of tasks with zero-shot or few-shot learning.\nMemory and Context Length: LLMs use attention mechanisms to capture long-range dependencies, whereas older models (e.g., LSTMs) struggled with understanding long contexts.\nTraining Methodology: LLMs are trained using self-supervised learning on massive datasets, followed by fine-tuning and reinforcement learning (e.g., RLHF) to boost performance (but not necessarily always).\nWhy do we call them \u0026quot;Large\u0026quot;? Parameter Size: LLMs typically have billions to trillions of parameters (e.g., GPT-4 has over 1 trillion parameters, Grok-3 has 2.7 trillion parameters).\nTraining Data: They are trained on massive, diverse text datasets covering a broad range of topics from across the internet.\nComputational Scale: Training an LLM requires thousands of GPUs/TPUs running for weeks or even months.\nNow, as I’m writing this blog, there are several different architectures for LLMs. When classifying LLMs by distinct architectures, we focus on the core structural designs that define how they process and generate language. Here are the main types\nTransformer Architecture Description: The transformer architecture is the backbone of most modern LLMs. It was introduced in the 2017 paper \u0026quot;Attention Is All You Need.\u0026quot; At its core is self-attention, a mechanism that lets the model figure out how important each word is in relation to others, no matter where they appear in a sentence. This was a huge step up from older models that processed text sequentially (word by word), because transformers can analyze entire sequences in parallel. That makes them faster and more efficient.\nThe architecture is built from stacked layers, each containing:\nMulti-head attention, which looks at different aspects of the input at the same time. Feed-forward neural networks, which further process the data. Transformers generally come in three main types:\nEncoder-Only: These models process the entire input bidirectionally looking at both past and future words in a sentence. That makes them great for understanding text or classifying sentiment (e.g., BERT).\nDecoder-Only: These work unidirectionally, meaning they predict the next word based only on what came before. This makes them ideal for generating text step by step (e.g., GPT-3).\nEncoder-Decoder: A hybrid approach where the encoder analyzes the input (like a sentence in one language), and the decoder generates an output (like a translation). This setup is perfect for tasks like translation or summarization (e.g., T5).\nKey Trait: Highly parallelizable, excels at capturing long-range dependencies via attention.\nExamples: GPT, BERT, T5.\nMixture of Experts (MoE) Architecture Description: The Mixture of Experts (MoE) approach is like having a team of specialists instead of a single jack-of-all-trades. Built on top of transformer layers, it breaks the model into multiple smaller sub-networks, or “experts,” each specializing in different types of inputs or tasks.\nA smart gating mechanism acts like a manager, deciding which experts to call on for a given piece of text. One expert might handle technical jargon, while another focuses on casual conversation. But here’s the trick: only a handful of experts activate at any given time, so the model doesn’t waste energy firing up all its billions of parameters at once. This sparse activation makes MoE models insanely efficient, especially as they scale to trillions of parameters.\nThe result? Faster processing and lower computational costs without sacrificing performance. While MoE isn’t as widely known as pure transformers, it’s gaining traction for its ability to balance power and efficiency, especially in massive models deployed for real-world applications.\nKey Trait: Sparse activation (only a subset of parameters is used per task, boosting efficiency at scale).\nExamples: Mixtral, Switch Transformers\nConvolutional Neural Network (CNN) Architecture Description: Convolutional Neural Networks (CNNs) are best known for image recognition, but they’ve also had a role in language processing. Though they’re not as dominant in today’s LLM-driven world. In NLP, CNNs work by sliding small filters over a sequence of text. Think of these filters as magnifying glasses that zoom in on short chunks of words or token embeddings (numerical representations of words). They detect local patterns like phrases or word combinations and stack them into higher-level features.\nUnlike transformers, which can take in an entire sentence at once, CNNs have a fixed-size context window based on the filter size. That means they’re great at spotting nearby relationships but struggle with long-range dependencies, like connecting ideas across paragraphs. Historically, CNNs were used for tasks like text classification (e.g., spotting spam emails), but they’ve mostly been overshadowed by transformers in large-scale NLP.\nThat said, hybrid models like ConvBERT show that CNNs can still play a role, especially in scenarios where speed matters more than deep contextual understanding.\nKey Trait: Fixed-size context windows, computationally efficient but less common in modern LLMs due to limited long-range dependency modeling.\nExamples: TextCNN (used for classification), ConvBERT (a hybrid with transformers); rarely seen as standalone full-scale LLMs.\nRetrieval-Augmented Architecture Description: Retrieval-Augmented architectures are like LLMs with a built-in research assistant. Instead of relying solely on what they’ve memorized during training, these models can tap into external knowledge sources like databases, web pages, or structured knowledge graphs to pull in fresh or specialized information.\nHere’s how it works: The model combines a traditional language generator (usually a transformer) with a retrieval module that searches for relevant facts or documents based on the input. So if you ask about a recent event, instead of guessing from outdated training data, it might fetch info from a news archive. The retrieved data then gets woven into the model’s response, making it way more accurate for fact-heavy or domain-specific tasks; think legal research or medical Q\u0026amp;A.\nThis setup is super useful when a model’s built-in knowledge (everything it learned during training) isn’t enough. Say you ask about something brand new or really specific, like a news event from last week. The model might not have the answer just from memory. That’s where the retrieval part kicks in. It can pull in fresh details from outside sources, like a library or the internet. But this also makes things a bit trickier because the model has to mix what it already knows with what it just found, kind of like a chef adding a new ingredient to their signature dish.\nWhen it works, though, the results are spot-on. Systems like RAG (Retrieval-Augmented Generation) are great examples. They combine language skills with the ability to dig up the latest and most accurate info, making them perfect for tasks where staying precise and up-to-date really matters.\nKey Trait: Augments model capabilities with external data, useful for tasks requiring structured or factual accuracy.\nExamples: RAG (Retrieval-Augmented Generation), REALM.\n","link":"https://blog.sksoumik.com/artificial-intelligence/different-types-of-llms-architectures/","section":"artificial-intelligence","tags":["machine learning","data science","artificial-intelligence","nlp","system-design"],"title":"Types of LLM Architectures"},{"body":"","link":"https://blog.sksoumik.com/tags/generative-ai/","section":"tags","tags":null,"title":"generative-ai"},{"body":"","link":"https://blog.sksoumik.com/series/generative-ai/","section":"series","tags":null,"title":"generative-ai"},{"body":"","link":"https://blog.sksoumik.com/tags/generative-ai-models/","section":"tags","tags":null,"title":"generative-ai-models"},{"body":" Author: Sadman Kabir Soumik\nWhat is Generative AI? Generative AI is a subset of artificial intelligence that creates completely new content, like images, videos, music, and text. It uses machine learning to find patterns in existing data and make new content based on these patterns. There are many great generative AI tools today, like -\nChatGPT (talking to a smart chatbot) DALL-E and MidJourney (they can create pictures) GitHub Copilot (coding assistant) How does Generative AI work? Generative AI is a tech that uses deep learning (a branch of machine learning) algorithms to generate new data. These programs learn from large collections of existing data, such as photos or texts, and use that knowledge to create new content. There are many types of generative models, including:\nGenerative Adversarial Networks (GAN) Variational Auto-encoders (VAE) Diffusion Models Deep Auto-Regressive Models Generative Adversarial Networks (GANs) GANs consist of 2 neural networks that work together: a generator and a discriminator. The generator creates new data that is similar to the training data, and the discriminator evaluates whether the generated data is real or fake. The two networks are trained together, with the generator trying to create data that the discriminator cannot distinguish from real data.\nWhen training starts, the generator produces fake data, and the discriminator quickly learns to tell that it's fake:\nAs training progresses, the generator gets closer to producing output that can fool the discriminator:\nFinally, if generator training goes well, the discriminator gets worse at telling the difference between real and fake. It starts to classify fake data as real, and its accuracy decreases.\nHere's a picture of the whole system:\nBoth the generator and the discriminator are neural networks. The generator output is connected directly to the discriminator input. Through backpropagation, the discriminator's classification provides a signal that the generator uses to update its weights. Read more here.\nVariational Auto Encoders (VAE) An autoencoder and a variational autoencoder (VAE) are types of neural networks used for unsupervised learning tasks. They both consist of an encoder network and a decoder network. The encoder network maps the input data to a lower-dimensional latent space, while the decoder network maps the latent space back to the original data space.\nHowever, the main difference between them is how they generate the latent space. Autoencoders generate a fixed latent space, while VAEs generate a probabilistic latent space, which allows them to sample from the latent space during the decoding process, resulting in more diverse outputs than autoencoders.\nAdditionally, VAEs can learn the underlying distribution of the input data and generate new samples by sampling from the learned distribution in the latent space, making them especially useful for generating new, realistic data samples in applications such as image or music generation. On the other hand, autoencoders are better suited for tasks such as feature extraction and data compression.\nDiffusion Models You probably heard of tools like DALL-E, which was released by OpenAI. DALL-E 2 uses a type of diffusion model.\nGAN models are often characterized by unstable training and limited diversity in generation because of their adversarial training approach. VAE relies on a surrogate loss, while flow models require specialized architectures to construct reversible transforms.\nDiffusion models are a type of machine learning model that use non-equilibrium thermodynamics as inspiration. They create a step-by-step process to add random noise to data, and then learn how to reverse that process in order to generate samples that match the original data. Unlike other models, such as VAE or flow models, diffusion models use a fixed learning procedure and the latent variable has high dimensionality, meaning it has the same number of dimensions as the original data. Read more from here.\nDiffusion models are machine learning systems that are trained to denoise random gaussian noise step by step, to get to a sample of interest, such as an image. The underlying model, often a neural network, is trained to predict a way to slightly denoise the image in each step. After certain number of steps, a sample is obtained.\nThe process is illustrated by the following design:\nThe architecture of the neural network, referred to as model, commonly follows the UNet architecture as proposed in this paper and improved upon in the Pixel++ paper.\nSome of the highlights of the architecture are:\nThe model outputs images of the same size as the input. The input image is processed through several blocks of ResNet layers, which reduces its size by half. The image is then processed through the same number of blocks that upsample it again. The architecture has skip connections which link features on the downsample path to corresponding layers in the upsample path, improving the quality of the output. Some examples of Diffusion models are:\nDALL-E Midjourney Stable Diffusion Imagen Deep Auto-regressive Models \u0026quot;Auto-regressive\u0026quot; models are a type of statistical model that uses past values of a time series to predict future values. The basic idea behind auto-regressive models is that the value of a time series at any given time point is a function of its previous values. In other words, the value at time t depends on the values at times t-1, t-2, t-3, and so on.\nAuto-regressive models are commonly used in time series forecasting applications, such as predicting stock prices, weather patterns, or traffic volumes. One example of an auto-regressive model is the Auto-Regressive Integrated Moving Average (ARIMA) model.\nOn the other hand, deep autoregressive models, which are a type of neural network that can generate sequential data such as text, speech or images. Deep autoregressive models are different from recurrent neural networks (RNNs) type of sequential models because they do not have feedback loops or hidden states. Instead, they use feed-forward layers and conditional probabilities to model the dependencies between inputs and outputs. Deep autoregressive models are also different from generative adversarial networks (GANs) or variational autoencoders (VAEs) because they do not rely on latent variables or adversarial training. Instead, they use maximum likelihood estimation and teacher forcing to learn from supervised data.\nTransformers based architecture is an great example of deep autoregressive models. The Illustrated Transformer is an excellent blog on how Transformer works.\nSome examples of deep autoregressive models are:\nTransformers (GPT3/3.5 is based on Transformer architecture. ) PixelRNN WaveNet References:\n[1] https://lilianweng.github.io/posts/2021-07-11-diffusion-models/\n[2] https://developers.google.com/machine-learning/gan\n[3] https://github.com/huggingface/diffusers\n[4] https://data-science-blog.com/blog/2022/03/15/deep-autoregressive-models/\n","link":"https://blog.sksoumik.com/artificial-intelligence/generative-ai-explained-chatgpt-midjourney-dalle/","section":"artificial-intelligence","tags":["artificial-intelligence","generative-ai","generative-ai-models"],"title":"How Generative AI Works - ChatGPT, Midjourney, and Dall-E Demystified"},{"body":"","link":"https://blog.sksoumik.com/categories/algorithm-design/","section":"categories","tags":null,"title":"algorithm design"},{"body":"","link":"https://blog.sksoumik.com/tags/algorithms/","section":"tags","tags":null,"title":"algorithms"},{"body":"Blind-75 is a curated list of 75 LeetCode problems compiled by Yangshun Tay. The list can be accessed at this link.\nInitially, I solved each problem using a brute-force approach, which often resulted in a Time Limit Exceeded (TLE) error. Then, I solved each problem with an optimized method that was accepted on LeetCode. For every problem on the list, I have provided both the naive brute force solution and an optimized solution. Additionally, I have included their respective time and space complexities. These solutions are also available on my GitHub repository titled \u0026quot;leetrank\u0026quot;.\nTwo Sum Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.\n1Input: nums = [2,7,11,15], target = 9 2Output: [0,1] 3Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. Brute Force\n1class Solution: 2 def twoSum(self, nums: List[int], target: int) -\u0026gt; List[int]: 3 n = len(nums) 4 for i in range(n): 5 for j in range(i+1, n): 6 if nums[i] + nums[j] == target: 7 return [i, j] Time complexity: O(n^2); n is the length of the input array. Space complexity: O(1); as we are only using a few extra variables and not using any extra data structures to store intermediate results.\nOptimized\n1class Solution: 2 def twoSum(self, nums: List[int], target: int) -\u0026gt; List[int]: 3 4 num_map = {} # put numbers as key and index as values 5 # Enumerate through the list \u0026#39;nums\u0026#39;. \u0026#39;enumerate\u0026#39; gives us an 6 # index (idx) and the item at that index (num) 7\t# For example, if nums = [2, 7, 11, 15], 8 # after this loop, num_map = {2: 0, 7: 1, 11: 2, 15: 3} 9 for idx, num in enumerate(nums): 10 # key: value = number, index 11 num_map[num] = idx 12 13 for idx, num in enumerate(nums): 14 # Calculate the complement of the current number 15 # (i.e., the difference between \u0026#39;target\u0026#39; and this number). 16 com = target - num 17 # If the complement exists in the dictionary \u0026#39;num_map\u0026#39; 18 # and it\u0026#39;s not the current number... 19 if com in num_map and idx != num_map[com]: 20 return [idx, num_map[com]] Time Complexity: O(n); The code includes a single loop that iterates over the entirety of the input array 'arr' (where 'n' is the length of 'arr'). The operations within this loop - dictionary lookups and updates - each have a constant time complexity of O(1). Therefore, the overall time complexity remains linear, i.e., O(n).\nSpace Complexity: O(n); The code uses a dictionary to store the indices of all elements in the input array 'arr'. Since the size of this dictionary directly scales with the size of the input array 'arr', the space complexity is O(n).\nBest Time to Buy and Sell Stock You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.\nReturn the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.\n1Input: prices = [7,1,5,3,6,4] 2Output: 5 3Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. 4Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell. Brute Force\n1def maxProfit(prices: List[int]) -\u0026gt; int: 2 max_profit = 0 3 n = len(prices) 4 5 for i in range(n): 6 for j in range(i+1, n): 7 profit = prices[j] - prices[i] 8 if profit \u0026gt; max_profit: 9 max_profit = profit 10 11 return max_profit Time Complexity: O(n^2); The code runs two loops that go through all the items in the 'prices' array. Because the loops are inside each other, every item is checked with every other item, which takes more time if the array gets bigger.\nSpace Complexity: O(1); The code only uses a few extra things like 'i', 'j', 'profit', and 'max_profit' to keep track of the best way to sell stock. Because we don't use any extra space that grows with the size of the 'prices' array, the space used stays the same no matter how big the array is.\nOptimized\n1class Solution: 2 def maxProfit(self, prices): 3 max_profit = 0 4 min_price = prices[0] 5 6 for price in prices: 7 # iterate through the array and for each day, 8 # calculate the potential profit by subtracting the current min_price from the 9 # current price (price - min_price) 10\t# If the potential profit is greater than the current max_profit, 11 # we update max_profit to the new value. 12 max_profit = max(max_profit, price - min_price) 13 # If the current price is less than min_price, we update min_price to the new value. 14 min_price = min(min_price, price) 15 return max_profit This approach has a time complexity of O(n) and a space complexity of O(1).\nContains Duplicate Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct.\n1Input: nums = [1,2,3,1] 2Output: true Brute Force\n1def containsDuplicate(nums): 2 n = len(nums) 3 # compare each element with every other element that comes after it in the list. 4 for i in range(n): 5 for j in range(i+1, n): 6 if nums[i] == nums[j]: 7 return True 8 return False Time complexity is O(n^2).\nSpace complexity is O(1).\nOptimized\n1class Solution: 2 def containsDuplicate(self, nums: List[int]) -\u0026gt; bool: 3 # Create a set to store the numbers we have seen. 4 seen = set() 5 6 # Iterate over the numbers in the input list. 7 for num in nums: 8 # If we have seen the current number before, return True. 9 if num in seen: 10 return True 11 12 # Add the current number to the set of seen numbers. 13 seen.add(num) 14 15 # If we reach here, it means we haven\u0026#39;t seen any duplicates, so return False. 16 return False The time complexity of this code is linear in the length of the nums list. This is because we need to iterate over the list once and perform a constant-time set membership check on each element. The space complexity is also linear in the length of the nums list, because we need to store each element of the list in the seen set. So, O(n).\nWithout using extra space:\n1def containsDuplicate(nums): 2 nums.sort() # Sort the array to bring duplicates together 3 for i in range(1, len(nums)): 4 if nums[i] == nums[i-1]: 5 return True 6 return False It has O(1) space complexity, but the time complexity becomes O(n log n) due to the sorting operation, where 'n' is the length of the input array.\nProduct of Array Except Self Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i]. You must write an algorithm that runs in O(n) time and without using the division operation.\n1Input: nums = [1,2,3,4] 2Output: [24,12,8,6] Brute Force\n1def product_except_self(nums): 2 n = len(nums) 3 answer = [0] * n 4 for i in range(n): 5 product = 1 6 for j in range(n): 7 # multiply all elements except the current element 8 if i != j: 9 product *= nums[j] 10 answer[i] = product 11 return answer Optimized\n1class Solution: 2 def productExceptSelf(self, nums: List[int]) -\u0026gt; List[int]: 3 n = len(nums) 4 left, right, ans = [1] * n, [1] * n, [1] * n 5 6 # fill left array 7 for i in range(1, n): 8 left[i] = nums[i-1] * left[i-1] 9 10 # fill the right array 11 for i in reversed(range(n-1)): 12 right[i] = nums[i+1] * right[i+1] 13 14 # fill the ans array 15 for i in range(n): 16 ans[i] = left[i] * right[i] 17 18 return ans Time Complexity: O(n); The provided code runs three loops sequentially that each iterate over the entire input array. Since these loops don't nest and run independently in linear time with respect to the size of the input array, the overall time complexity remains linear, i.e., O(n).\nSpace Complexity: O(n); The code creates three separate arrays - the left array, the right array, and the result array. Each of these arrays is of the same size as the input array. Therefore, the space used is directly proportional to the size of the input array, leading to a space complexity of O(n).\nMaximum Subarray Given an integer array nums, find the subarray which has the largest sum and return its sum.\n1Input: nums = [-2,1,-3,4,-1,2,1,-5,4] 2Output: 6 3Explanation: [4,-1,2,1] has the largest sum = 6. Brute Force\n1def max_subarray_sum(nums): 2 n = len(nums) 3 max_sum = float(\u0026#39;-inf\u0026#39;) 4 for i in range(n): 5 for j in range(i, n): 6 subarray_sum = sum(nums[i:j+1]) 7 max_sum = max(max_sum, subarray_sum) 8 return max_sum Time Complexity: O(n^3); The code has two loops that work inside each other, which would typically result in O(n^2) complexity. However, inside the inner loop, the sum(nums[i:j+1]) function essentially creates a third loop because it sums over the sublist of 'nums'. So for each pair of 'i' and 'j', it's summing over the sublist, and this causes the time complexity to be cubic, i.e., O(n^3).\nSpace Complexity: O(1); This code only uses a few extra things like 'i', 'j', 'subarray_sum', and 'max_sum' to keep track of the highest total it has found so far. It does not make any new lists or dictionaries that grow with the size of the 'nums' list, so the space used stays the same no matter how big the list is.\nOptimized\n1class Solution: 2 def maxSubArray(self, nums: List[int]) -\u0026gt; int: 3 # Initialize the maximum sum with the first number in the input array. 4 max_sum = current_sum = nums[0] 5 6 # Loop through the numbers in the input array starting from the second number. 7 for num in nums[1:]: 8 # Update the current sum by taking the maximum of the current number 9 # and the current number plus the current sum. 10 current_sum = max(num, num + current_sum) 11 # Update the maximum sum with the maximum of the current sum and the maximum sum. 12 max_sum = max(max_sum, current_sum) 13 14 # Return the maximum sum. 15 return max_sum Time Complexity: O(n); The code looks at every number in the 'nums' list one by one, only once.\nSpace Complexity: O(1); The code just uses a few extra things like 'num', 'current_sum', and 'max_sum' to remember the highest total it has found. It doesn't make any new lists or dictionaries that get bigger with the size of the 'nums' list, so the space used stays the same no matter how big the list is.\nMaximum Product Subarray Given an integer array nums, find a subarray that has the largest product, and return the product.\n1Input: nums = [2,3,-2,4] 2Output: 6 3Explanation: [2,3] has the largest product 6. Brute Force\n1def max_subarray_product(nums): 2 n = len(nums) 3 max_product = float(\u0026#39;-inf\u0026#39;) 4 for i in range(n): 5 for j in range(i, n): 6 subarray_product = 1 7 for k in range(i, j+1): 8 subarray_product *= nums[k] 9 max_product = max(max_product, subarray_product) 10 return max_product This solution has a time complexity of O(n^3) since we are considering every possible subarray and calculating its product using a nested loop.\nOptimized\n1class Solution: 2 def maxProduct(self, nums: List[int]) -\u0026gt; int: 3 max_product = min_product = nums[0] 4 5 # initialize the maximum product found so far to the first element 6 max_so_far = nums[0] 7 8 # iterate through the array, starting from the second element 9 for i in range(1, len(nums)): 10 # Find the maximum and minimum product of the current element and the current product 11 max_product, min_product = ( 12 max(nums[i], max_product * nums[i], min_product * nums[i]), 13 min(nums[i], max_product * nums[i], min_product * nums[i]), 14 ) 15 16 # update the maximum product found so far 17 max_so_far = max(max_so_far, max_product) 18 19 return max_so_far Time complexity: O(n), n is the number of elements in the input array, because the for loop iterates over all elements in the input array. Space complexity: O(1), because it only uses a constant amount of memory to store a few variables, regardless of the size of the input.\nFind Minimum in Rotated Sorted Array Suppose an array of length n sorted in ascending order is rotated between 1 and n times. For example, the array nums = [0,1,2,4,5,6,7] might become:\n[4,5,6,7,0,1,2] if it was rotated 4 times. [0,1,2,4,5,6,7] if it was rotated 7 times. Notice that rotating an array [a[0], a[1], a[2], ..., a[n-1]] 1 time results in the array [a[n-1], a[0], a[1], a[2], ..., a[n-2]].\nGiven the sorted rotated array nums of unique elements, return the minimum element of this array.\nYou must write an algorithm that runs in O(log n) time.\n1Input: nums = [3,4,5,1,2] 2Output: 1 3Explanation: The original array was [1,2,3,4,5] rotated 3 times. Brute Force\n1def findMin(nums): 2 # Since the array is already sorted in ascending order before rotation, 3 # the first element we encounter which is smaller than the previous element will be the minimum element. 4 for i in range(1, len(nums)): 5 if nums[i] \u0026lt; nums[i-1]: 6 return nums[i] 7 return nums[0] This is O(n) time complexity solution.\nOptimized\n1class Solution: 2 def findMin(self, nums: List[int]) -\u0026gt; int: 3 # Set the left and right indices for the binary search 4 left, right = 0, len(nums) - 1 5 6 # Perform binary search to find the minimum value 7 while left \u0026lt; right: 8 # Calculate the midpoint of the current search range 9 mid = (left + right) // 2 10 11 # If the midpoint value is greater than the rightmost value, 12 # the minimum value must be in the right half of the array 13 if nums[mid] \u0026gt; nums[right]: 14 left = mid + 1 15 else: 16 # If the midpoint value is not greater than the rightmost value, 17 # the minimum value must be in the left half of the array 18 right = mid 19 20 # Return the minimum value, which will be at the left index 21 return nums[left] The time complexity of the above code is O(log n), where n is the length of the array. This is because the binary search reduces the search range by half in each iteration, so the number of iterations required is determined by the number of times n can be divided by 2 before reaching 1. The space complexity is O(1), since the code only uses a constant number of variables regardless of the size of the input array.\nSearch in Rotated Sorted Array There is an integer array nums sorted in ascending order (with distinct values). Given the array nums after the possible rotation and an integer target, return the index of target if it is in nums, or -1 if it is not in nums.\nYou must write an algorithm with O(log n) runtime complexity.\n1Input: nums = [4,5,6,7,0,1,2], target = 0 2Output: 4 Brute Force\n1class Solution: 2 def search(self, nums: List[int], target: int) -\u0026gt; int: 3 for i in range(len(nums)): 4 if nums[i] == target: 5 return i 6 return -1 Optimized\n1class Solution: 2 def search(self, nums: List[int], target: int) -\u0026gt; int: 3 4 # Initialize left and right pointers 5 left = 0 6 right = len(nums) - 1 7 8 # Keep looping until left pointer is less than or equal to right pointer 9 while left \u0026lt;= right: 10 # Calculate middle index 11 mid = (left + right) // 2 12 13 # If target is at middle index, return middle index 14 if target == nums[mid]: 15 return mid 16 17 # If left side is sorted 18 if nums[left] \u0026lt;= nums[mid]: 19 # If target is in left side, update right pointer to middle index - 1 20 if nums[left] \u0026lt;= target \u0026lt;= nums[mid]: 21 right = mid - 1 22 # Else, update left pointer to middle index + 1 23 else: 24 left = mid + 1 25 # Else, right side is sorted 26 else: 27 # If target is in right side, update left pointer to middle index + 1 28 if nums[mid] \u0026lt;= target \u0026lt;= nums[right]: 29 left = mid + 1 30 # Else, update right pointer to middle index - 1 31 else: 32 right = mid - 1 33 34 # Target not found, return -1 35 return -1 The time complexity of the above code is O(log n) because in each iteration, the search space is halved. This is because the code uses binary search to find the target element in the given list.\nThe space complexity of the above code is O(1), because the code does not use any additional memory space proportional to the size of the input. The only variables used are the left and right pointers, which remain constant regardless of the size of the input list.\n3 Sum Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.\nNotice that the solution set must not contain duplicate triplets.\n1Input: nums = [-1,0,1,2,-1,-4] 2Output: [[-1,-1,2],[-1,0,1]] 3Explanation: 4nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. 5nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. 6nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. 7The distinct triplets are [-1,0,1] and [-1,-1,2]. 8Notice that the order of the output and the order of the triplets does not matter. Brute Force\n1class Solution: 2 def threeSum(self, nums: List[int]) -\u0026gt; List[List[int]]: 3 n = len(nums) 4 result = [] 5 seen = set() 6 7 for i in range(n): 8 for j in range(i+1, n): 9 for k in range(j+1, n): 10 11 if nums[i] + nums[j] + nums[k] == 0: 12 triplet = tuple(sorted([nums[i], nums[j], nums[k]])) 13 14 if triplet not in seen: 15 seen.add(triplet) 16 result.append([nums[i], nums[j], nums[k]]) 17 return result The time complexity of the above solution is O(n^3), where n is the length of the input array nums. This is because we are using three nested loops to generate all possible triplets of elements from the input array, and checking the sum of each triplet.\nThe space complexity of the solution is O(1), because we are not using any additional data structures that depend on the input size. The only additional memory we use is for the result list to store the valid triplets, but the size of the list is bounded by the number of valid triplets, which is at most n^3 for an input array of length n. So the space used by the result list is also O(n^3).\nOptimized\n1class Solution: 2 def threeSum(self, nums: List[int]) -\u0026gt; List[List[int]]: 3 # Sort the input list 4 nums.sort() 5 6 n = len(nums) 7 8 # Initialize empty list to store triplets 9 triplets = [] 10 11 # If the length of the input list is less than 3, return empty list 12 if n \u0026lt; 3: 13 return [] 14 15 # Loop through all elements in the list except the last two 16 for i in range(n - 2): 17 # Initialize left pointer to the element after current element 18 left = i + 1 19 # Initialize right pointer to the last element in the list 20 right = n - 1 21 22 # Keep looping until left pointer is less than right pointer 23 while left \u0026lt; right: 24 # Calculate the sum of the current element, left element, and right element 25 sums3 = nums[i] + nums[left] + nums[right] 26 # If the sum is 0, append the current triplet to the triplets list and update the left and right pointers 27 if sums3 == 0: 28 triplets.append((nums[i], nums[left], nums[right])) 29 left += 1 30 right -= 1 31 # If the sum is less than 0, update the left pointer 32 elif sums3 \u0026lt; 0: 33 left += 1 34 # Else, update the right pointer 35 else: 36 right -= 1 37 38 # Convert the triplets list to a set to remove duplicates, and then convert it back to a list 39 triplets = list(set(triplets)) 40 # Return the triplets list 41 return triplets The time complexity of the above code is O(n^2) and the space complexity is O(n).\nContainer With Most Water You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).\nFind two lines that together with the x-axis form a container, such that the container contains the most water. Return the maximum amount of water a container can store.\nNotice that you may not slant the container.\n1Input: height = [1,8,6,2,5,4,8,3,7] 2Output: 49 3Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. Brute Force\nOne naive solution to this problem is to use a brute force approach where we consider all possible pairs of lines and calculate the area of water contained by each pair of lines. Then, we return the maximum area.\n1def maxArea(height): 2 n = len(height) 3 max_area = 0 4 for i in range(n): 5 for j in range(i+1, n): 6 area = min(height[i], height[j]) * (j - i) 7 max_area = max(max_area, area) 8 return max_area It has a time complexity of O(n^2) as we are considering all possible pairs of lines.\nOptimized\n1class Solution: 2 def maxArea(self, height: List[int]) -\u0026gt; int: 3 # Initialize left and right pointers at the start and end of the list 4 left = 0 5 right = len(height) - 1 6 7 # Initialize the maximum area to 0 8 max_area = 0 9 10 # Loop through the list while the left pointer is less than the right pointer 11 while left \u0026lt; right: 12 # Calculate the width of the rectangle by subtracting the left pointer from the right pointer 13 w = right - left 14 15 # Calculate the height of the rectangle by taking the minimum of the values at the left and right pointers 16 h = min(height[left], height[right]) 17 18 # Calculate the area of the rectangle by multiplying the height and width 19 area = h * w 20 21 # Update the maximum area by comparing the current area to the previous maximum 22 max_area = max(max_area, area) 23 24 # If the value at the left pointer is less than the value at the right pointer, move the left pointer right 25 # Otherwise, move the right pointer left 26 if height[left] \u0026lt; height[right]: 27 left += 1 28 else: 29 right -= 1 30 31 # Return the maximum area 32 return max_area The time complexity of this code is O(n), where n is the length of the height list. This is because the left and right pointers are iterated over the list once, and in each iteration the pointers move either left or right by one position.\nThe space complexity of this code is O(1), because the space used by the code is constant and does not depend on the size of the input. The only variables that are created are a few integers, which have a constant size regardless of the input size.\nSum of Two Integers Given two integers a and b, return the sum of the two integers without using the operators + and -\n1Input: a = 1, b = 2 2Output: 3 1class Solution: 2 def getSum(self, a: int, b: int) -\u0026gt; int: 3 while b != 0: 4 carry = a \u0026amp; b 5 a = a ^ b 6 b = carry \u0026lt;\u0026lt; 1 7 return a The time complexity of the code is O(n), where n is the number of bits needed to represent the numbers a and b in binary. This is because the time complexity of the code is directly proportional to the number of bits needed to represent a and b.\nThe space complexity of the code is O(n), where n is the maximum depth of the recursion stack. This is because at each recursive call, a new frame is added to the stack, and the maximum depth of the stack is equal to the number of bits needed to represent the numbers a and b in binary.\nNumber of 1 Bits Write a function that takes an unsigned integer and returns the number of '1' bits it has (also known as the Hamming weight).\n1Input: n = 00000000000000000000000000001011 2Output: 3 3Explanation: The input binary string 00000000000000000000000000001011 has a total of three \u0026#39;1\u0026#39; bits. 1class Solution: 2 def hammingWeight(self, n: int) -\u0026gt; int: 3 # Initialize a count variable to keep track of the number of bits in n that are set to 1 4 count = 0 5 6 # Loop until n is 0 7 while n != 0: 8 # Check if the least significant bit of n is set to 1 9 # If it is, increment count by 1 10 count += n \u0026amp; 1 11 12 # Shift the bits in n to the right by 1 13 # This effectively divides n by 2 and discards any remainder 14 n \u0026gt;\u0026gt;= 1 15 16 # Return the final value of count as the result 17 return count 18 19 # another approach using built-in bin 20 # return bin(n).count(\u0026#39;1\u0026#39;) The time complexity of the code is linear in the number of bits in the binary representation of n. This is because the loop continues until n is equal to 0, and the number of bits in the binary representation of n determines how many times the loop will run. Therefore, the time complexity of the code is O(b), where b is the number of bits in the binary representation of n.\nThe space complexity of the code is constant. This is because the only variable that is used to store any data is the count variable, which does not depend on the input n and always takes up the same amount of space. Therefore, the space complexity of the code is O(1).\nCounting Bits Given an integer n, return an array ans of length n + 1 such that for each i (0 \u0026lt;= i \u0026lt;= n), ans[i] *is the number of* 1*'s in the binary representation of* i.\n1Input: n = 5 2Output: [0,1,1,2,1,2] 3Explanation: 40 --\u0026gt; 0 51 --\u0026gt; 1 62 --\u0026gt; 10 73 --\u0026gt; 11 84 --\u0026gt; 100 95 --\u0026gt; 101 1class Solution: 2 def countBits(self, n: int) -\u0026gt; List[int]: 3 # create an array with n+1 elements 4 # to store the number of bits of each number from 0 to n 5 ans = [0] * (n + 1) 6 # loop through each number from 1 to n 7 for i in range(1, n + 1): 8 # find the number of bits of i by dividing i by 2 9 # and storing the result in the index i in the array 10 # this is done by right-shifting the binary representation of i by 1 bit 11 # which is equivalent to dividing i by 2 12 ans[i] = ans[i \u0026gt;\u0026gt; 1] 13 # add the remainder of the division by 2 to the number of bits of i 14 # this is done by performing a bitwise AND operation between i and 1 15 # the result is either 0 or 1 depending on the least significant bit of i 16 # if the least significant bit of i is 0, the result is 0 17 # if the least significant bit of i is 1, the result is 1 18 ans[i] += (i \u0026amp; 1) 19 return ans 20 21 # return [bin(i).count(\u0026#39;1\u0026#39;) for i in range(n + 1)] The time complexity of the solution is also O(n) because the loop runs n times, and the operations inside the loop have a constant time complexity. Therefore, the overall time complexity of the solution is O(n).\nThe space complexity of the solution is O(n) because the size of the array ans is directly proportional to the input n.\nMissing Number Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.\n1Input: nums = [3,0,1] 2Output: 2 3Explanation: n = 3 since there are 3 numbers, so all numbers are in the range [0,3]. 2 is the missing number in the range since it does not appear in nums. Brute Force\n1class Solution: 2 def missingNumber(self, nums: List[int]) -\u0026gt; int: 3 n = len(nums) 4 for i in range(n+1): 5 if i not in nums: 6 return i The time complexity of the above solution is O(n^2), where n is the length of the input array nums. This is because we are looping through a range of numbers from 0 to n+1, and for each number, we are checking whether it is present in the input array nums using the not in operator, which takes O(n) time in the worst case.\nThe space complexity of the above solution is O(1), because we are not using any additional data structures to store information about the input array.\nOptimized\nThe sum of the first n natural numbers can be calculated using the formula n * (n+1) / 2. Therefore, if we subtract the sum of the input array nums from the sum of the first n natural numbers, we can get the missing number.\n1def find_missing_number(nums): 2 n = len(nums) 3 expected_sum = n * (n+1) // 2 4 actual_sum = sum(nums) 5 return expected_sum - actual_sum The time complexity of this optimized solution is O(n), which is much faster than the brute force solution. The space complexity is O(1), which is the same as the brute force solution.\nReverse Bits Reverse bits of a given 32 bits unsigned integer.\n1Input: n = 00000010100101000001111010011100 2Output: 964176192 (00111001011110000010100101000000) 3Explanation: The input binary string 00000010100101000001111010011100 represents the unsigned integer 43261596, so return 964176192 which its binary representation is 00111001011110000010100101000000. 1class Solution: 2 def reverseBits(self, n: int) -\u0026gt; int: 3 # Initialize the result to 0 4 ans = 0 5 6 # Loop through the 32 bits in the integer 7 for i in range(32): 8 # Add the last bit of n to the result and shift it left by 1 9 # This puts the next bit in the last bit position 10 ans = (ans \u0026lt;\u0026lt; 1) + (n \u0026amp; 1) 11 12 # Shift n right by 1 to get the next bit 13 n \u0026gt;\u0026gt;= 1 14 15 # Return the reversed bits 16 return ans The time complexity of the above code is O(1), because the number of operations performed is constant and does not depend on the input size. This is because the code always performs the same number of iterations (32) and the number of operations per iteration is also constant.\nThe space complexity of the above code is also O(1), because the amount of memory used does not depend on the input size. This is because the only variable that is allocated memory is ans, which has a fixed size of 32 bits regardless of the input size.\nClimbing Stairs You are climbing a staircase. It takes n steps to reach the top.\nEach time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?\n1Input: n = 3 2Output: 3 3Explanation: There are three ways to climb to the top. 41. 1 step + 1 step + 1 step 52. 1 step + 2 steps 63. 2 steps + 1 step 1from functools import lru_cache 2 3class Solution: 4 # Decorate the climbStairs method with lru_cache 5 # to enable memoization 6 @lru_cache 7 def climbStairs(self, n: int) -\u0026gt; int: 8 # If n is less than 3, return n 9 # This covers the cases where n is 0, 1, or 2 10 if n \u0026lt; 3: 11 return n 12 13 # Otherwise, return the sum of the number of ways to climb 14 # n - 1 stairs and n - 2 stairs 15 else: 16 return self.climbStairs(n-1) + self.climbStairs(n-2) The time complexity of the above code is O(n), because the number of operations performed depends on the input size. This is because the climbStairs method is called recursively, and the number of recursive calls grows linearly with the input size.\nThe space complexity of the above code is also O(n), because the amount of memory used depends on the input size. This is because the lru_cache decorator stores the results of each climbStairs call in a cache, and the size of the cache grows linearly with the input size.\nCoin Change You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.\nReturn the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.\nYou may assume that you have an infinite number of each kind of coin.\n1Input: coins = [1,2,5], amount = 11 2Output: 3 3Explanation: 11 = 5 + 5 + 1 Solution 1\n1# functools.lru_cache leverages dynamic programming concepts by using memoization 2from functools import lru_cache 3 4class Solution: 5 def coinChange(self, coins: List[int], amount: int) -\u0026gt; int: 6 @lru_cache(maxsize=None) 7 def find_min_coins(amount): 8 # Base case: if amount is 0, no coins are needed, so return 0 9 if amount == 0: 10 return 0 11 12 # Base case: if amount is negative, return infinity 13 # This signifies that the current coin cannot be used to sum up to the given amount 14 if amount \u0026lt; 0: 15 return float(\u0026#34;inf\u0026#34;) 16 17 # This acts as a placeholder for the minimum number of coins needed to make up the amount 18 min_coin = float(\u0026#34;inf\u0026#34;) 19 20 # Iterate over each coin in the list 21 # The goal is to try to subtract each coin value from the total amount 22 # and recursively solve the problem for the remaining amount 23 for coin in coins: 24 # For each coin, subtract its value from the total amount, and recursively call 25 # find_min_coins for the remaining amount, then add 1 to represent the coin just used 26 result = find_min_coins(amount - coin) + 1 27 # Update min_coins to be the smaller of the current min_coins and the new result 28 # This ensures that min_coins always holds the smallest number of coins found so far 29 min_coin = min(min_coin, result) 30 return min_coin 31 32 result = find_min_coins(amount) 33 if result == float(\u0026#34;inf\u0026#34;): 34 return -1 35 else: 36 return result Time and space complexity: O(amount * n), where n is the number of different coin denominations and amount is the target amount. For each coin, we are performing a subproblem for 'amount' times. Hence, the total time complexity will be O(amount * n).\nSolution 2\n1# memoization without lru_cache 2class Solution: 3 def coinChange(self, coins: List[int], amount: int) -\u0026gt; int: 4 # memo dictionary to store the minimum number of coins for each amount. 5 memo = {0: 0} 6 7 def find_min_coins(amount): 8 # same memoization implementation without lru_cache 9 if amount in memo: 10 return memo[amount] 11 12 # if amount is negative, there\u0026#39;s no solution 13 if amount \u0026lt; 0: 14 return float(\u0026#34;inf\u0026#34;) 15 16 min_coin = float(\u0026#34;inf\u0026#34;) 17 18 for coin in coins: 19 result = find_min_coins(amount - coin) + 1 20 min_coin = min(min_coin, result) 21 22 memo[amount] = min_coin 23 return min_coin 24 25 result = find_min_coins(amount) 26 if result == float(\u0026#34;inf\u0026#34;): 27 return -1 28 else: 29 return result Time and space complexity: O(amount * n), where n is the number of different coin denominations and amount is the target amount. For each coin, we are performing a subproblem for 'amount' times. Hence, the total time complexity will be O(amount * n).\nLongest Increasing Subsequence Given an integer array nums, return *the length of the longest strictly increasing*.\n1Input: nums = [10,9,2,5,3,7,101,18] 2Output: 4 3Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. Brute Force\n1# generating all possible subsequences and checking which ones are increasing 2from functools import combinations 3 4class Solution: 5 def lengthOfLIS(self, nums: List[int]) -\u0026gt; int: 6 # helper function to check if the subsequence is strictly increasing 7 def is_increasing(seq): 8 for i in range(len(seq) - 1): 9 if seq[i] \u0026gt;= seq[i + 1]: 10 return False 11 return True 12 13 longest = 0 14\t# Go through all possible subsequences 15 for i in range(1, len(nums) + 1): 16 for subseq in combinations(nums, i): 17 # if the subsequence is increasing and its length is greater than the current longest 18 if is_increasing(subseq) and len(subseq) \u0026gt; longest: 19 longest = len(subseq) 20 21 return longest Time complexity: O(n * 2^n) itertools.combinations(): 2^n is_increasing(): is called, which in the worst-case scenario, iterates over every element of the subsequence. O(n)\nOptimized: DP(Tabulation)\n1def lengthOfLIS(nums): 2 # Initialize a tabulation table of length n with all 1s 3 n = len(nums) 4 # Initialize a list \u0026#39;table\u0026#39; of size n, and fill it with 1s. This list will store 5 # the length of the longest increasing subsequence (LIS) that ends at each element. 6 # We start with 1 because a single element is itself a valid increasing subsequence. 7 table = [1] * n 8 9 # Iterate over all elements in nums and compute the length of the 10 # longest increasing subsequence ending at each element 11 for i in range(1, n): 12 for j in range(i): 13 if nums[j] \u0026lt; nums[i]: 14 table[i] = max(table[i], table[j] + 1) 15 16 # Return the maximum length of increasing subsequence 17 return max(table) Time complexity: O(n^2) and space complexity: O(n)\nOptimized: DP(Memoization)\n1from functools import lru_cache 2 3class Solution: 4 def lengthOfLIS(self, nums: List[int]) -\u0026gt; int: 5 @lru_cache(maxsize=None) 6 def LIS_ending_at(i): 7 # base case: The LIS ending at index 0 has length of 1 8 if i == 0: 9 return 1 10 11 # The LIS ending at i has atleast nums[i] itself 12 # so the length is atleast 1 13 longest = 1 14 15 for j in range(i): 16 if nums[j] \u0026lt; nums[i]: 17 res = LIS_ending_at(j) + 1 18 longest = max(longest, res) 19 return longest 20 21 # corner case 22 if not nums: 23 return 0 24 25 # find the length of the LIS ending at each position and return the max 26 return max(LIS_ending_at(i) for i in range(len(nums))) Longest Common Subsequence Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.\n1Input: text1 = \u0026#34;abcde\u0026#34;, text2 = \u0026#34;ace\u0026#34; 2Output: 3 3Explanation: The longest common subsequence is \u0026#34;ace\u0026#34; and its length is 3. 1class Solution: 2 def longestCommonSubsequence(self, text1: str, text2: str) -\u0026gt; int: 3 # Define the helper function with lru_cache 4 @lru_cache(maxsize=None) 5 def lcs_helper(i, j): 6 # If we have reached the end of one of the input strings, return 0 7 if i == len(text1) or j == len(text2): 8 return 0 9 # If the characters at the current positions in the two input strings are the same, 10 # add 1 to the result and move on to the next characters in both strings 11 if text1[i] == text2[j]: 12 return 1 + lcs_helper(i + 1, j + 1) 13 # If the characters do not match, recursively call the function in two ways: 14 # once skipping one character in the first string, 15 # and once skipping one character in the second string 16 return max(lcs_helper(i + 1, j), lcs_helper(i, j + 1)) 17 18 # Return the result of calling the helper function with the starting indices (0, 0) 19 return lcs_helper(0, 0) The time and space complexity of the code depend on the size of the input strings text1 and text2. Because the function uses memoization with lru_cache, the time complexity is effectively O(n * m), where n and m are the lengths of text1 and text2, respectively. The space complexity is also O(n * m) because the lru_cache stores the results of the function calls in a dictionary, which takes up space proportional to the number of function calls made.\nWord Break Problem Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words.\nNote that the same word in the dictionary may be reused multiple times in the segmentation.\n1Input: s = \u0026#34;leetcode\u0026#34;, wordDict = [\u0026#34;leet\u0026#34;,\u0026#34;code\u0026#34;] 2Output: true 3Explanation: Return true because \u0026#34;leetcode\u0026#34; can be segmented as \u0026#34;leet code\u0026#34;. 1from functools import lru_cache 2 3class Solution: 4 def wordBreak(self, s, wordDict): 5 # convert the word dictionary from a list to a set for faster lookup (O(1) complexity) 6 wordDict = set(wordDict) 7 8 # helper function, \u0026#39;wb\u0026#39;, to perform the recursive word break check 9 # with lru_cache 10 @lru_cache(maxsize=None) 11 def checkSegmentation(start): 12 # Base case: if the \u0026#39;start\u0026#39; index has reached the end of the string, 13 # it means the string \u0026#39;s\u0026#39; can be segmented into words from the dictionary, 14 # thus return True 15 if start == len(s): 16 return True 17 18 # iterate over all possible \u0026#39;end\u0026#39; indices of substrings 19 # of \u0026#39;s\u0026#39; starting from \u0026#39;start\u0026#39; 20 for end in range(start+1, len(s) + 1): 21 # If the substring from \u0026#39;start\u0026#39; to \u0026#39;end\u0026#39; is in the dictionary, 22 # and if the remainder of the string after \u0026#39;end\u0026#39; can also be 23 # segmented into words in the dictionary 24 # (this is checked recursively), return True 25 if s[start:end] in wordDict and checkSegmentation(end): 26 return True 27 28 # If no valid segmentation is found after checking 29 # all possible substrings from \u0026#39;start\u0026#39;, return False 30 return False 31 32 return checkSegmentation(0) Time complexity: O(n^2), where n is the length of the string. We're still looking at every possible substring, which is an O(n^2) operation, but due to the lru_cache decorator, previously calculated results are stored and reused, preventing redundant computation.\nCombination Sum IV Given an array of distinct integers nums and a target integer target, return the number of possible combinations that add up to target.\n1Input: nums = [1,2,3], target = 4 2Output: 7 3Explanation: 4The possible combination ways are: 5(1, 1, 1, 1) 6(1, 1, 2) 7(1, 2, 1) 8(1, 3) 9(2, 1, 1) 10(2, 2) 11(3, 1) 12Note that different sequences are counted as different combinations. 1from functools import lru_cache 2 3class Solution: 4 def combinationSum4(self, nums: List[int], target: int) -\u0026gt; int: 5 @lru_cache(maxsize=None) 6 def dp(tar): 7 if tar == 0: 8 return 1 9 elif tar \u0026lt; 0: 10 return 0 11 else: 12 result = 0 13 for num in nums: 14 result += dp(tar - num) 15 return result 16 return dp(target) Time complexity: O(target * n), where 'target' is the target sum we're aiming for and 'n' is the size of the input list 'nums'. This is because for each possible sum up to the target.\nSpace complexity: O(target) because of the recursion stack in the depth-first search. In the worst case, the recursion goes as deep as the value of 'target', hence 'target' stack frames are used. The LRU cache also uses O(target) space to store the result for each possible sum up to the target. So, the total space complexity is O(target).\nHouse Robber You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and it will automatically contact the police if two adjacent houses were broken into on the same night.\nGiven an integer array nums representing the amount of money of each house, return *the maximum amount of money you can rob tonight without alerting the police*.\n1Input: nums = [1,2,3,1] 2Output: 4 3Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). 4Total amount you can rob = 1 + 3 = 4. 1from functools import lru_cache 2 3class Solution: 4 def rob(self, nums): 5 @lru_cache(maxsize=None) # Cache the results of the recursive calls 6 def robFrom(i): 7 if i \u0026gt;= len(nums): 8 return 0 9 # there are 2 possible options 10 # option 1: 11 # total amount of money the robber can get if he choose to rob the current house (i). 12 # In this scenario, he gain the money in the current house (nums[i]), 13 # but they he to skip the next house due to the security systems 14 # so we add the maximum possible loot from the house i+2 15 # option 2: 16 # total amount of money the robber can get if they choose to skip the current house (i). 17 # In this scenario, he doesn\u0026#39;t get the money in the current house 18 return max(robFrom(i+2) + nums[i], robFrom(i+1)) 19 20 return robFrom(0) Time complexity: each subproblem is solved only once, and the result is stored for later use. Therefore, the time complexity is reduced to linear, specifically O(n), where n is the size of the input list 'nums'.\nSpace complexity: The space complexity is also O(n), where n is the size of the input list 'nums'. This is due to the additional space used by the cache to store the results of the subproblems.\nHouse Robber II You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night.\nGiven an integer array nums representing the amount of money of each house, return *the maximum amount of money you can rob tonight without alerting the police*.\n1Input: nums = [2,3,2] 2Output: 3 3Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses. 1class Solution: 2 # This method finds the maximum amount of money that can be robbed from the houses in the given list of numbers 3 # It returns the maximum amount of money that can be robbed 4 def rob(self, nums: List[int]) -\u0026gt; int: 5 # If there is only one house, the maximum amount of money that can be robbed is the value of that house 6 if len(nums) == 1: 7 return nums[0] 8 9 # Otherwise, find the maximum amount of money that can be robbed if the first or last house is not robbed 10 # Return the maximum of these two values 11 return max(self.rob1(nums[:-1]), self.rob1(nums[1:])) 12 13 14 # This method finds the maximum amount of money that can be robbed from the houses in the given list of numbers 15 # It returns the maximum amount of money that can be robbed 16 def rob1(self, nums: List[int]) -\u0026gt; int: 17 # Initialize variables to store the maximum amount of money that can be robbed at the current and previous houses 18 rob1, rob2 = 0, 0 19 20 # For each house, find the maximum amount of money that can be robbed at the current house 21 for num in nums: 22 # The maximum amount of money that can be robbed at the current house is the maximum of the sum of the money 23 # from the previous house and the value of the current house, and the maximum amount of money that can be 24 # robbed at the previous house 25 rob1, rob2 = rob2, max(rob1 + num, rob2) 26 27 # Return the maximum amount of money that can be robbed at the last house 28 return rob2 The time complexity of the above code is O(n), where n is the number of houses in the given list of numbers. This is because the rob1() method iterates through the entire list of houses, and the rob() method calls the rob1() method twice, each time with a list of houses that has a length of n-1.\nThe space complexity of the above code is O(1), because the rob1() method only uses a constant amount of additional memory to store the variables rob1 and rob2.\nDecode Ways Given a string s containing only digits, return the number of ways to decode it.\nA message containing letters from A-Z can be encoded into numbers using the following mapping:\n1\u0026#39;A\u0026#39; -\u0026gt; \u0026#34;1\u0026#34; 2\u0026#39;B\u0026#39; -\u0026gt; \u0026#34;2\u0026#34; 3... 4\u0026#39;Z\u0026#39; -\u0026gt; \u0026#34;26\u0026#34; To decode an encoded message, all the digits must be grouped then mapped back into letters using the reverse of the mapping above (there may be multiple ways). For example, \u0026quot;11106\u0026quot; can be mapped into:\n\u0026quot;AAJF\u0026quot; with the grouping (1 1 10 6) \u0026quot;KJF\u0026quot; with the grouping (11 10 6) Note that the grouping (1 11 06) is invalid because \u0026quot;06\u0026quot; cannot be mapped into 'F' since \u0026quot;6\u0026quot; is different from \u0026quot;06\u0026quot;.\n1Input: s = \u0026#34;12\u0026#34; 2Output: 2 3Explanation: \u0026#34;12\u0026#34; could be decoded as \u0026#34;AB\u0026#34; (1 2) or \u0026#34;L\u0026#34; (12). 1class Solution: 2 def numDecodings(self, s: str) -\u0026gt; int: 3 # If the string is empty or None, there are no possible decodings 4 if len(s) == 0 or s is None: 5 return 0 6 7 # Define a recursive function that uses memoization to improve performance 8 @lru_cache(maxsize=None) 9 def dfs(st): 10 # If the string is empty, there is only one possible decoding 11 if len(st) == 0: 12 return 1 13 # If the string starts with a zero, there are no possible decodings 14 if st[0] == \u0026#34;0\u0026#34;: 15 return 0 16 # If the string has only one character, there is only one possible decoding 17 if len(st) == 1: 18 return 1 19 # If the first two characters of the string can be decoded together (i.e. are less than or equal to 26), 20 # then we can consider both decodings: one that decodes the first two characters together, and one that 21 # decodes the first character by itself. Otherwise, we can only consider the single decoding that decodes 22 # the first character by itself. 23 if int(st[:2]) \u0026lt;= 26: 24 return dfs(st[1:]) + dfs(st[2:]) 25 else: 26 return dfs(st[1:]) 27 28 # Call the recursive function with the original string and return the result 29 return dfs(s) The time complexity of the above code is O(2^n), where n is the length of the input string. This is because each recursive call branches into two additional recursive calls, and each branch is considered independently. This means that for a string of length n, there will be 2^n total recursive calls.\nThe space complexity of the above code is O(n), where n is the length of the input string. This is because the dfs function uses memoization to store the results of previous recursive calls, and the memoization table will store at most one result for each possible input string. Since the length of the input string determines the number of possible input strings, the space complexity is O(n).\nJump Game You are given an integer array nums. You are initially positioned at the array's first index, and each element in the array represents your maximum jump length at that position.\nReturn true if you can reach the last index, or false otherwise.\n1Input: nums = [2,3,1,1,4] 2Output: true 3Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index. 1class Solution: 2 # greedy approach 3 def canJump(self, nums: List[int]) -\u0026gt; bool: 4 goal = len(nums) - 1 # last index 5 6 # for i in reversed(range(goal)): 7 for idx in range(goal, -1, -1): 8 if idx + nums[idx] \u0026gt;= goal: 9 # we have added the current index to the jump length, 10 # because at any given index, futhest we can reach is the current index + jump length 11 goal = idx 12 13 # return True if goal == 0 else False 14 return goal == 0 The time complexity of the code is O(n), where n is the length of the nums list, because the for loop iterates over all elements in the list.\nThe space complexity of the code is O(1), because the number of variables used does not depend on the size of the input and remains constant. The variables used are goal (1 variable), idx (1 variable), and nums (1 variable, which references the input list and is not counted as a separate variable in the space complexity calculation).\nCourse Schedule There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.\nFor example, the pair [0, 1], indicates that to take course 0 you have to first take course 1. Return true if you can finish all courses. Otherwise, return false.\n1Input: numCourses = 2, prerequisites = [[1,0]] 2Output: true 3Explanation: There are a total of 2 courses to take. 4To take course 1 you should have finished course 0. So it is possible. 1class Solution: 2 # video explanation: https://youtu.be/EgI5nU9etnU 3 def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -\u0026gt; bool: 4 # define a adjacency list to represent the graph and store the prerequisites 5 # put empty list for each node initially 6 # keys: course number 7 # values: list of prerequisites 8 adj_map = {i: [] for i in range(numCourses)} 9 # {0: [], 1: []} 10 11 # add the prerequisites to the adjacency list 12 # c: course, p: prerequisites 13 for c, p in prerequisites: 14 # add course as key and pre_req as value 15 adj_map[c].append(p) 16 17 # adj_map = {0: [1], 1: [0]} 18 19 # track the visited nodes to check if there is a cycle 20 visited = set() 21 22 # apply dfs to determine if there is a cycle 23 # v: course, adj_map: adjacency list 24 # stack, because we are implementing dfs 25 def hasCycle(v, stack): 26 # if the node is already visited, return true 27 if v in visited: 28 if v in stack: 29 return True 30 return False 31 32 # mark the node as visited 33 visited.add(v) 34 # add the node to the stack 35 stack.append(v) 36 37 # check if there is a cycle in the graph 38 # check for all values in the adjacency list of the node 39 for pre_req in adj_map[v]: 40 if hasCycle(pre_req, stack): 41 return True 42 43 # remove the node from the stack 44 stack.pop() 45 return False 46 47 # check if there is a cycle in the graph 48 for v in range(numCourses): 49 if hasCycle(v, []): 50 # if hasCycle returns true, there is a cycle, 51 # so we cannot finish all courses 52 return False 53 54 return True The time complexity of the code is O(V+E), where V is the number of courses (vertices) and E is the number of prerequisites (edges). This is because the hasCycle function is called once for each course, and for each course, it visits all of its prerequisites, at most once.\nThe space complexity of the code is O(V), because at most, the call stack will contain all the courses.\nNumber of Islands Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands.\nAn island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.\n1Input: grid = [ 2 [\u0026#34;1\u0026#34;,\u0026#34;1\u0026#34;,\u0026#34;1\u0026#34;,\u0026#34;1\u0026#34;,\u0026#34;0\u0026#34;], 3 [\u0026#34;1\u0026#34;,\u0026#34;1\u0026#34;,\u0026#34;0\u0026#34;,\u0026#34;1\u0026#34;,\u0026#34;0\u0026#34;], 4 [\u0026#34;1\u0026#34;,\u0026#34;1\u0026#34;,\u0026#34;0\u0026#34;,\u0026#34;0\u0026#34;,\u0026#34;0\u0026#34;], 5 [\u0026#34;0\u0026#34;,\u0026#34;0\u0026#34;,\u0026#34;0\u0026#34;,\u0026#34;0\u0026#34;,\u0026#34;0\u0026#34;] 6] 7Output: 1 1class Solution: 2 # video explanation: https://youtu.be/ZixJexAaOAk?t=474 3 def numIslands(self, grid: List[List[str]]) -\u0026gt; int: 4 5 # number of rows 6 rows = len(grid) 7 # number of cols 8 cols = len(grid[0]) 9 10 count = 0 11 12 for i in range(rows): 13 for j in range(cols): 14 if grid[i][j] == \u0026#34;1\u0026#34;: 15 self.dfs(i, j, rows, cols, grid) 16 count += 1 17 return count 18 19 def dfs(self, i, j, rows, cols, grid): 20 # This is checking if the current index is out of bounds or if the current index is not a 1. 21 if i \u0026gt;= rows or i \u0026lt; 0 or j \u0026gt;= cols or j \u0026lt; 0 or grid[i][j] == \u0026#34;0\u0026#34;: 22 return 0 23 24 # Use # that modifies the input to ensure that the count isn\u0026#39;t incremented where we could accidentally 25 # traverse the same \u0026#39;1\u0026#39; cell multiple times and get into an infinite loop within an island 26 # it\u0026#39;s basically a implicit way of marking the visited square/nodes instead of putting the 27 # visited nodes in an visited array 28 grid[i][j] = \u0026#34;0\u0026#34; 29 30 # top 31 self.dfs(i, j + 1, rows, cols, grid) 32 # bottom 33 self.dfs(i, j - 1, rows, cols, grid) 34 # left 35 self.dfs(i - 1, j, rows, cols, grid) 36 # right 37 self.dfs(i + 1, j, rows, cols, grid) The time complexity of the code is O(R x C), where R is the number of rows and C is the number of columns in the grid. This is because the dfs function is called once for each cell in the grid, and for each cell, it visits all of its neighbors, at most once.\nThe space complexity of the code is O(R x C), because at most, the call stack will contain all the cells in the grid.\nInsert Interval You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval newInterval = [start, end] that represents the start and end of another interval.\nInsert newInterval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary).\nReturn intervals after the insertion.\n1Input: intervals = [[1,3],[6,9]], newInterval = [2,5] 2Output: [[1,5],[6,9]] 1class Solution: 2 def insert(self, intervals: List[List[int]], newInterval: List[int]) -\u0026gt; List[List[int]]: 3 # Add the new interval to the list of intervals 4 intervals.append(newInterval) 5 # Sort the intervals by their start time 6 intervals.sort(key=lambda x: x[0]) 7 8 # Initialize the output list with the first interval in the sorted list 9 output = [intervals[0]] 10 # Iterate over the remaining intervals in the sorted list 11 for i in range(1, len(intervals)): 12 # If the current interval overlaps with the last interval in the output list 13 # Update the last interval in the output list to include the current interval 14 if intervals[i][0] \u0026lt;= output[-1][1]: 15 output[-1][1] = max(output[-1][1], intervals[i][1]) 16 # If the current interval doesn\u0026#39;t overlap with the last interval in the output list 17 # Add the current interval to the output list 18 else: 19 output.append(intervals[i]) 20 21 # Return the output list 22 return output The time complexity of the code is O(N log N), where N is the number of intervals. This is because sorting the intervals takes O(N log N) time and the remaining operations take O(N) time.\nThe space complexity of the code is O(N), because at most, the output list will contain all the intervals.\nMerge Intervals Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.\n1Input: intervals = [[1,3],[2,6],[8,10],[15,18]] 2Output: [[1,6],[8,10],[15,18]] 3Explanation: Since intervals [1,3] and [2,6] overlap, merge them into [1,6]. 1class Solution: 2 3 # This function takes a list of intervals as input and returns a list of 4 # intervals with overlapping intervals merged. 5 # The intervals are sorted by their starting coordinate before merging. 6 def merge(self, intervals: List[List[int]]) -\u0026gt; List[List[int]]): 7 8 # sort the intervals by their starting coordinate 9 intervals.sort(key=lambda x: x[0]) 10 11 # initialize the output with the first interval in the sorted list 12 output = [intervals[0]] 13 14 # iterate through the rest of the intervals in the sorted list 15 for i in range(1, len(intervals)): 16 17 # if the current interval\u0026#39;s start coordinate is less than or equal to the 18 # end coordinate of the last interval in the output list 19 # then the two intervals overlap and need to be merged 20 if intervals[i][0] \u0026lt;= output[-1][1]: 21 22 # merge the current interval with the last interval in the output list 23 # by updating the end coordinate of the last interval 24 # with the maximum of its current value and the end coordinate of the current interval 25 output[-1][1] = max(output[-1][1], intervals[i][1]) 26 27 # if the current interval\u0026#39;s start coordinate is greater than the end coordinate 28 # of the last interval in the output list 29 # then the two intervals do not overlap and the current interval can be added to the output list as is 30 else: 31 output.append(intervals[i]) 32 33 # return the list of merged intervals 34 return output The time complexity of the code is O(n _ log(n)), where n is the number of intervals in the input list. This is because the intervals are sorted by their starting coordinate using the sort() method, which has a time complexity of O(n _ log(n)).\nThe space complexity of the code is O(n), where n is the number of intervals in the input list. This is because the output list is constructed by iterating through the input list and adding intervals to it, which requires storing a total of n intervals in the output list.\nReverse Linked List Given the head of a singly linked list, reverse the list, and return the reversed list.\n1Input: head = [1,2,3,4,5] 2Output: [5,4,3,2,1] 1class Solution: 2 def reverseList(self, head: Optional[ListNode]) -\u0026gt; Optional[ListNode]: 3 \u0026#34;\u0026#34;\u0026#34; 4 - The linked list is reversed in place, so no additional space is used for the reversed list. 5 - The function works by iterating through the linked list and reversing the links between the nodes. 6 - The original head of the list becomes the tail of the reversed list, and the original tail becomes the head. 7 \u0026#34;\u0026#34;\u0026#34; 8 9 # Initialize the previous node as None and the current node as the head of the list 10 previous, current = None, head 11 12 # Iterate through the linked list until we reach the end 13 while current: 14 # Reverse the link by pointing the current node\u0026#39;s next reference to the previous node 15 # Then, update the previous node to the current node and the current node to the next node in the list 16 current.next, previous, current = previous, current, current.next 17 18 # Return the previous node, which is now the head of the reversed list 19 return previous The time complexity of the above code is O(n), where n is the number of nodes in the linked list. This is because the function iterates through the entire linked list once to reverse the links between the nodes.\nThe space complexity of the above code is O(1), as the function reverses the linked list in place and does not use any additional space. It only uses a few variables (previous, current) to store references to nodes in the linked list.\nLinked List Cycle Given head, the head of a linked list, determine if the linked list has a cycle in it.\nThere is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter.\nReturn true if there is a cycle in the linked list. Otherwise, return false.\n1Input: head = [3,2,0,-4], pos = 1 2Output: true 3Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed). 1class Solution: 2 def hasCycle(self, head: Optional[ListNode]) -\u0026gt; bool: 3 \u0026#34;\u0026#34;\u0026#34; 4 - A linked list has a cycle if any node in the list appears more than once. 5 - This function uses the \u0026#34;Floyd\u0026#39;s cycle-finding algorithm\u0026#34;, also known as the \u0026#34;tortoise and hare algorithm\u0026#34;. 6 - It uses two pointers, \u0026#34;slow\u0026#34; and \u0026#34;fast\u0026#34;, that move through the linked list at different speeds. 7 - If there is a cycle, the fast pointer will eventually catch up to the slow pointer. 8 \u0026#34;\u0026#34;\u0026#34; 9 10 # Initialize both pointers to the head of the linked list 11 slow = fast = head 12 13 # Iterate through the linked list until the fast pointer reaches the end 14 while fast and fast.next: 15 # Move the slow pointer one node at a time 16 slow = slow.next 17 # Move the fast pointer two nodes at a time 18 fast = fast.next.next 19 20 # If the slow and fast pointers are pointing to the same node, there is a cycle in the linked list 21 if slow == fast: 22 return True 23 24 # If the fast pointer reached the end of the linked list, there is no cycle 25 return False The time complexity of the above code is O(n), where n is the number of nodes in the linked list. In the worst case, the fast pointer will iterate through the entire linked list and the slow pointer will iterate through half of the linked list.\nThe space complexity of the above code is O(1), as the function only uses a few variables (slow, fast) to store references to nodes in the linked list and does not use any additional space.\nMerge Two Sorted Lists You are given the heads of two sorted linked lists list1 and list2.\nMerge the two lists in a one sorted list. The list should be made by splicing together the nodes of the first two lists.\nReturn the head of the merged linked list.\n1Input: list1 = [1,2,4], list2 = [1,3,4] 2Output: [1,1,2,3,4,4] 1class Solution: 2 def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -\u0026gt; Optional[ListNode]: 3 \u0026#34;\u0026#34;\u0026#34; 4 - This function uses recursion to repeatedly merge the two linked lists, starting with the smallest nodes. 5 - If either of the input lists is empty, the function returns the other list. 6 - Otherwise, it compares the values of the two list heads and appends the smaller one to the merged list. 7 \u0026#34;\u0026#34;\u0026#34; 8 9 # If either of the lists is empty, return the other list 10 if not list1 or not list2: 11 return list1 or list2 12 13 # Compare the values of the two list heads 14 # If the value of list1 is smaller, set the next node of list1 to the result of merging the next 15 # nodes of list1 and list2 16 # Otherwise, set the next node of list2 to the result of merging list1 and the next nodes of list2 17 if list1.val \u0026lt; list2.val: 18 list1.next = self.mergeTwoLists(list1.next, list2) 19 return list1 20 else: 21 list2.next = self.mergeTwoLists(list1, list2.next) 22 return list2 The time complexity of the above code is O(n), where n is the total number of nodes in the two linked lists. This is because the function performs a constant amount of work for each node in the lists.\nThe space complexity of the above code is O(n), as the function uses recursion and the call stack may grow up to the size of the larger of the two input linked lists. However, since the function is merging the linked lists in place and not creating a new list, the space complexity could also be considered O(1).\nSpiral Matrix Given an m x n matrix, return all elements of the matrix in spiral order.\n1Input: matrix = [[1,2,3],[4,5,6],[7,8,9]] 2Output: [1,2,3,6,9,8,7,4,5] 1class Solution: 2 def spiralOrder(self, matrix: List[List[int]]) -\u0026gt; List[int]: 3 \u0026#34;\u0026#34;\u0026#34; 4 - The function iteratively removes the first row of the matrix and 5 - then rotates the remaining matrix 90 degrees clockwise. 6 - This process is repeated until the matrix is empty. 7 \u0026#34;\u0026#34;\u0026#34; 8 9 result = [] 10 11 # While the matrix is not empty 12 while matrix: 13 14 # Add the elements of the first row of the matrix to the result list 15 result.extend(matrix.pop(0)) 16 17 # If the matrix is empty, break the loop 18 if not matrix: 19 break 20 21 # Rotate the matrix 90 degrees clockwise 22 # This is done by transposing the matrix and reversing each row 23 matrix = [*zip(*matrix)][::-1] 24 25 # Return the result list 26 return result The time complexity of the above code is O(n), where n is the total number of elements in the matrix. This is because the function iterates through each element of the matrix once.\nThe space complexity of the above code is O(n), as the function creates a new list to store the elements of the matrix in spiral order. The size of the list will be the same as the number of elements in the matrix.\nRotate Image You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).\nYou have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.\n1Input: matrix = [[1,2,3],[4,5,6],[7,8,9]] 2Output: [[7,4,1],[8,5,2],[9,6,3]] 1class Solution: 2 def rotate(self, matrix: List[List[int]]) -\u0026gt; None: 3 \u0026#34;\u0026#34;\u0026#34; 4 - The function first reverses the matrix horizontally and then transposes it. 5 - Reversing the matrix horizontally is equivalent to rotating it 270 degrees clockwise. 6 - Transposing the matrix swaps the rows and columns, which is equivalent to rotating it 90 degrees clockwise. 7 \u0026#34;\u0026#34;\u0026#34; 8 9 # Reverse the matrix horizontally 10 matrix.reverse() 11 12 # Transpose the matrix 13 # This is done by swapping the elements at (i, j) and (j, i) for all i and j 14 for i in range(len(matrix)): 15 for j in range(i): 16 matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] Bonus : rotate the image by 90 degrees (anti-clockwise)\n1def rotate(matrix): 2 for i in range(len(matrix) - 1, -1, -1): 3 for j in range(i): 4 matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] 5 matrix.reverse() The time complexity of the above code is O(n^2), where n is the number of rows and columns in the matrix. This is because the function iterates through each element of the matrix once to reverse it horizontally and again to transpose it.\nThe space complexity of the above code is O(1), as the function modifies the matrix in place and does not use any additional space.\nWord Search Given an m x n grid of characters board and a string word, return true if word exists in the grid.\nThe word can be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once.\n1Input: board = [[\u0026#34;A\u0026#34;,\u0026#34;B\u0026#34;,\u0026#34;C\u0026#34;,\u0026#34;E\u0026#34;],[\u0026#34;S\u0026#34;,\u0026#34;F\u0026#34;,\u0026#34;C\u0026#34;,\u0026#34;S\u0026#34;],[\u0026#34;A\u0026#34;,\u0026#34;D\u0026#34;,\u0026#34;E\u0026#34;,\u0026#34;E\u0026#34;]], word = \u0026#34;ABCCED\u0026#34; 2Output: true 1class Solution: 2 def exist(self, board: List[List[str]], word: str) -\u0026gt; bool: 3 \u0026#34;\u0026#34;\u0026#34; 4 - The function uses depth-first search to explore the possible paths through the matrix. 5 - It uses a set, \u0026#34;path\u0026#34;, to keep track of the letters that have already been visited. 6 - If the current letter in the matrix matches the current letter in the word, 7 the function continues the search in the four adjacent cells. 8 - If the search reaches the end of the word, the function returns True. 9 - If the search reaches a cell that is out of bounds, already visited, 10 or has a different letter, the function returns False. 11 \u0026#34;\u0026#34;\u0026#34; 12 13 # Get the number of rows and columns in the matrix 14 rows = len(board) 15 cols = len(board[0]) 16 17 # Initialize a set to keep track of the visited cells 18 path = set() 19 20 # Define the recursive function for depth-first search 21 def dfs(r, c, w): 22 # If the search has reached the end of the word, return True 23 if w == len(word): 24 return True 25 26 # If the current cell is out of bounds, already visited, or has a different letter, return False 27 if ( 28 r \u0026lt; 0 29 or r \u0026gt;= rows 30 or c \u0026lt; 0 31 or c \u0026gt;= cols 32 or (r, c) in path 33 or word[w] != board[r][c] 34 ): 35 return False 36 37 # Mark the current cell as visited 38 path.add((r, c)) 39 40 res = ( 41 dfs(r + 1, c, w+1) 42 or dfs(r - 1, c, w+1) 43 or dfs(r, c + 1, w+1) 44 or dfs(r, c - 1, w+1) 45 ) 46 47 path.remove((r, c)) 48 return res 49 50 for r in range(rows): 51 for c in range(cols): 52 if dfs(r, c, 0): 53 return True 54 return False The time complexity of the above code is O(nm * 4^k), where n and m are the number of rows and columns in the matrix, respectively, and where k is the length of the word being searched for. This is because at each step of the search, the algorithm has four possible options to choose from: it can move to the cell above, below, to the left, or to the right. If the search continues until it reaches the end of the word, the function will have made 4^k recursive calls.\nThe space complexity of the above code is O(k), as the function uses a set to store the visited cells, and the size of the set will be at most k.\nWord Search II Given an m x n board of characters and a list of strings words, return all words on the board.\nEach word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.\n1Input: board = [[\u0026#34;o\u0026#34;,\u0026#34;a\u0026#34;,\u0026#34;a\u0026#34;,\u0026#34;n\u0026#34;],[\u0026#34;e\u0026#34;,\u0026#34;t\u0026#34;,\u0026#34;a\u0026#34;,\u0026#34;e\u0026#34;],[\u0026#34;i\u0026#34;,\u0026#34;h\u0026#34;,\u0026#34;k\u0026#34;,\u0026#34;r\u0026#34;],[\u0026#34;i\u0026#34;,\u0026#34;f\u0026#34;,\u0026#34;l\u0026#34;,\u0026#34;v\u0026#34;]], words = [\u0026#34;oath\u0026#34;,\u0026#34;pea\u0026#34;,\u0026#34;eat\u0026#34;,\u0026#34;rain\u0026#34;] 2Output: [\u0026#34;eat\u0026#34;,\u0026#34;oath\u0026#34;] 1class Solution: 2 def findWords(self, board: List[List[str]], words: List[str]) -\u0026gt; List[str]: 3 # Get the number of rows and columns in the board 4 rows = len(board) 5 cols = len(board[0]) 6 7 # Initialize a set to store visited cells during the search 8 path = set() 9 10 # Initialize a set to store the words found in the board 11 result = set() 12 13 def dfs(r, c, w, word): 14 # If we have reached the end of the word, add it to the result set 15 if w == len(word): 16 result.add(word) 17 return 18 19 # If the current cell is out of bounds, already visited, or the 20 # character at the cell does not match the current character in the word, 21 # return without searching further 22 if ( 23 r \u0026lt; 0 24 or r \u0026gt;= rows 25 or c \u0026lt; 0 26 or c \u0026gt;= cols 27 or (r, c) in path 28 or word[w] != board[r][c] 29 ): 30 return 31 32 # Add the current cell to the visited set 33 path.add((r, c)) 34 35 # Search in all four directions from the current cell 36 dfs(r + 1, c, w + 1, word) 37 dfs(r - 1, c, w + 1, word) 38 dfs(r, c + 1, w + 1, word) 39 dfs(r, c - 1, w + 1, word) 40 41 # Remove the current cell from the visited set 42 path.remove((r, c)) 43 44 # Iterate through each cell in the board 45 for r in range(rows): 46 for c in range(cols): 47 # For each word in the list of words, start a depth-first search from 48 # the current cell to find the word in the board 49 for word in words: 50 dfs(r, c, 0, word) 51 52 # Return the list of words found in the board 53 return list(result) The time complexity of the above code is O(mn * 4^l), where m and n are the number of rows and columns in the board, and l is the length of the longest word in the list of words.\nThis is because the outer loop runs in O(mn) time, and the inner loop runs in O(l) time. The dfs function is called once for each cell in the board and each character in the word, so it runs in O(4^l) time in the worst case, when the word is not found and the function needs to search in all four directions from each cell.\nThe space complexity of the code is O(mn + l), as the path set used to store visited cells during the search takes O(mn) space, and the word parameter of the dfs function takes O(l) space.\nLongest Substring Without Repeating Characters Given a string s, find the length of the longest substring without repeating characters.\n1Input: s = \u0026#34;abcabcbb\u0026#34; 2Output: 3 3Explanation: The answer is \u0026#34;abc\u0026#34;, with the length of 3. 1class Solution: 2 def lengthOfLongestSubstring(self, s: str) -\u0026gt; int: 3 \u0026#34;\u0026#34;\u0026#34; 4 - The function uses a sliding window approach to keep track of the current substring. 5 - It maintains a set, \u0026#34;char_set\u0026#34;, of the characters in the current substring. 6 - It uses two pointers, \u0026#34;left\u0026#34; and \u0026#34;right\u0026#34;, to define the boundaries of the window. 7 - If the character at the right pointer is already in the set, the function removes 8 the character at the left pointer and moves it to the right. 9 - The function updates the maximum length of the substring every time the right pointer moves. 10 \u0026#34;\u0026#34;\u0026#34; 11 12 # initialize an empty set to keep track of the characters in the current substring 13 char_set = set() 14 15 # initialize the left and right pointers 16 left = 0 17 max_len = 0 18 19 # Iterate through the string with the right pointer 20 for right in range(len(s)): 21 22 # While the character at the right pointer is in the set 23 while s[right] in char_set: 24 25 # Remove the character at the left pointer from the set 26 char_set.remove(s[left]) 27 28 # Move the left pointer to the right 29 left += 1 30 31 # Add the character at the right pointer to the set 32 char_set.add(s[right]) 33 34 # Update the maximum length of the substring 35 max_len = max(max_len, len(char_set)) 36 37 # Return the maximum length of the substring 38 return max_len The time complexity of the above code is O(n), where n is the length of the input string. This is because the function uses a sliding window approach, which involves iterating through the string once with the right pointer and updating the left pointer and the set of characters in the current substring.\nThe space complexity of the above code is O(k), where k is the size of the set of characters in the current substring. This is because the function uses a set to store the characters in the current substring, and the size of the set will be at most k.\nValid Anagram Given two strings s and t, return true if t is an anagram of s, and false otherwise.\nAn Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.\n1Input: s = \u0026#34;anagram\u0026#34;, t = \u0026#34;nagaram\u0026#34; 2Output: true 1class Solution: 2 def isAnagram(self, s: str, t: str) -\u0026gt; bool: 3 \u0026#34;\u0026#34;\u0026#34; 4 - An anagram is a word or phrase formed by rearranging the letters of a different word or phrase. 5 - The function first checks if the two strings have the same length. If not, they cannot be anagrams. 6 - It then sorts both strings and compares the characters at each position. 7 - If any of the characters do not match, the function returns False. 8 - Otherwise, it returns True. 9 \u0026#34;\u0026#34;\u0026#34; 10 11 # Get the length of the two strings 12 x1 = len(s) 13 x2 = len(t) 14 15 # If the lengths are different, the strings cannot be anagrams 16 if x1 != x2: 17 return False 18 19 # Sort the two strings 20 s = sorted(s) 21 t = sorted(t) 22 23 # Compare the characters at each position 24 for i in range(0,x1): 25 # If any of the characters do not match, return False 26 if s[i] != t[i]: 27 return False 28 29 # If all characters match, return True 30 return True The time complexity of the above code is O(nlogn), where n is the length of the input strings. This is because the function sorts the two strings using the built-in sorted function, which has a time complexity of O(nlogn).\nThe space complexity of the above code is O(n), where n is the length of the input strings. This is because the function creates two new sorted strings, which have a combined size of 2n.\nSolving the same problem using O(n) time:\n1from collections import Counter 2 3class Solution: 4 def isAnagram(self, s: str, t: str) -\u0026gt; bool: 5 return len(s) == len(t) and Counter(s) == Counter(t) The time complexity of the above code is O(n), where n is the length of the input strings. This is because the function uses the Counter function from the collections module, which has a time complexity of O(n) for constructing the counter objects and a time complexity of O(1) for comparing them.\nThe space complexity of the above code is O(n), where n is the length of the input strings. This is because the function creates two Counter objects, which have a combined size of 2n.\nGroup Anagrams Given an array of strings strs, group the anagrams together. You can return the answer in any order.\nAn Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.\n1Input: strs = [\u0026#34;eat\u0026#34;,\u0026#34;tea\u0026#34;,\u0026#34;tan\u0026#34;,\u0026#34;ate\u0026#34;,\u0026#34;nat\u0026#34;,\u0026#34;bat\u0026#34;] 2Output: [[\u0026#34;bat\u0026#34;],[\u0026#34;nat\u0026#34;,\u0026#34;tan\u0026#34;],[\u0026#34;ate\u0026#34;,\u0026#34;eat\u0026#34;,\u0026#34;tea\u0026#34;]] 1class Solution: 2 def groupAnagrams(self, strs: List[str]) -\u0026gt; List[List[str]]: 3 \u0026#34;\u0026#34;\u0026#34; 4 - The function creates a dictionary to store the groups of anagrams. 5 - It iterates through the input strings and sorts each string. 6 - If the sorted string is not in the dictionary, it creates a new group with the original string. 7 - If the sorted string is in the dictionary, it appends the original string to the corresponding group. 8 - Finally, it returns the values of the dictionary as a list of lists. 9 \u0026#34;\u0026#34;\u0026#34; 10 11 # Create a dictionary to store the groups of anagrams 12 anagrams = {} 13 14 # Iterate through the input strings 15 for s in strs: 16 # Sort the string 17 sorted_s = \u0026#34;\u0026#34;.join(sorted(s)) 18 19 # If the sorted string is not in the dictionary, create a new group with the original string 20 if sorted_s not in anagrams: 21 anagrams[sorted_s] = [s] 22 # If the sorted string is in the dictionary, append the original string to the corresponding group 23 else: 24 anagrams[sorted_s] = [s] 25 26 return list(anagrams.values()) The time complexity of the above code is O(nmlogn), where n is the number of strings in the input list and m is the average length of the strings. This is because the function sorts each string, which has a time complexity of O(mlogn).\nThe space complexity of the above code is O(nm), where n is the number of strings in the input list and m is the average length of the strings. This is because the function stores each original string in the dictionary, which has a combined size of nm.\nValid Parentheses Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.\nAn input string is valid if:\nOpen brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order. Every close bracket has a corresponding open bracket of the same type. 1Input: s = \u0026#34;()[]{}\u0026#34; 2Output: true Solution 1\n1class Solution: 2 def isValid(self, s: str) -\u0026gt; bool: 3 # use stack data structure to keep track of the opening parentheses 4 stack = [] 5 6 # iterate over each char in the string 7 for c in s: 8 # if the char in opening parentheses 9 if c in [\u0026#34;(\u0026#34;, \u0026#34;{\u0026#34;, \u0026#34;[\u0026#34;]: 10 # push it onto the stack 11 stack.append(c) 12 # if the char is a closing parentheses 13 else: 14 # we check if the stack is empty. If it is empty, 15 # there is no corresponding opening parenthesis, so we return False 16 if not stack: 17 return False 18 # if the stack is not empty 19 # compare the current closing parenthesis with the top element of the stack. 20 # If they form a valid pair, we pop the opening parenthesis from the stack. 21 if c == \u0026#34;)\u0026#34; and stack[-1] == \u0026#34;(\u0026#34;: 22 stack.pop() 23 24 elif c == \u0026#34;}\u0026#34; and stack[-1] == \u0026#34;{\u0026#34;: 25 stack.pop() 26 27 elif c == \u0026#34;]\u0026#34; and stack[-1] == \u0026#34;[\u0026#34;: 28 stack.pop() 29 else: 30 # If the character is not a valid closing parenthesis or does not match the 31 # top of the stack, we return False. 32 return False 33 34\t# after iterating through all the characters, we check if the stack is empty. 35 # If it is, it means all the opening parentheses have been closed, and the string is valid 36 return len(stack) == 0 The time complexity of the above code is O(n), where n is the length of the input string. This is because the function iterates through the characters in the string once.\nThe space complexity of the above code is O(n), where n is the length of the input string. This is because the function stores the opening parentheses, brackets, and curly braces in a stack, which has a size of n at most.\nSolution 2\n1# iteratively removing valid pairs of parentheses from the string until no more valid 2# pairs can be found. If the resulting string is empty, then the input string is valid. 3class Solution: 4 def isValid(self, s: str) -\u0026gt; bool: 5 while \u0026#34;()\u0026#34; in s or \u0026#34;[]\u0026#34; in s or \u0026#34;{}\u0026#34; in s: 6 s = s.replace(\u0026#34;()\u0026#34;, \u0026#34;\u0026#34;).replace(\u0026#34;[]\u0026#34;, \u0026#34;\u0026#34;).replace(\u0026#34;{}\u0026#34;, \u0026#34;\u0026#34;) 7 return s == \u0026#34;\u0026#34; Valid Palindrome A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.\nGiven a string s, return true if it is a palindrome, or false otherwise.\n1Input: s = \u0026#34;A man, a plan, a canal: Panama\u0026#34; 2Output: true 3Explanation: \u0026#34;amanaplanacanalpanama\u0026#34; is a palindrome. 1class Solution: 2 def isPalindrome(self, s: str) -\u0026gt; bool: 3 clean_s = \u0026#34;\u0026#34; 4 5 for c in s: 6 if c.isalnum(): 7 clean_s += c.lower() 8 9 return clean_s == clean_s[::-1] The time complexity of the above code is O(n), where n is the length of the input string s. This is because the code iterates through the string once to convert it to lowercase and once to filter out non-alphanumeric characters.\nThe space complexity of the above code is also O(n), as a new list is created that has the same length as the input string. This is because the list comprehension [c for c in s if c.isalnum()] creates a new list that contains all alphanumeric characters from the input string.\nLongest Palindromic Substring Given a string s, return the longest palindromic substring in s.\n1Input: s = \u0026#34;babad\u0026#34; 2Output: \u0026#34;bab\u0026#34; 3Explanation: \u0026#34;aba\u0026#34; is also a valid answer. 1class Solution: 2 def longestPalindrome(self, s: str) -\u0026gt; str: 3 # Initialize an empty string to store the longest palindrome found so far 4 longest_palindrome = \u0026#34;\u0026#34; 5 6 # Iterate through each character in the input string 7 for i in range(len(s)): 8 # Check if the substring centered at this character is a palindrome 9 # with an odd length (e.g. \u0026#34;aba\u0026#34;) 10 odd_palindrome = self.isPalindrome(s, i, i) 11 12 # Check if the substring centered at this character is a palindrome 13 # with an even length (e.g. \u0026#34;abba\u0026#34;) 14 even_palindrome = self.isPalindrome(s, i, i+1) 15 16 # Update the longest palindrome if either of these substrings is longer 17 # than the current longest palindrome 18 if len(odd_palindrome) \u0026gt; len(longest_palindrome): 19 longest_palindrome = odd_palindrome 20 if len(even_palindrome) \u0026gt; len(longest_palindrome): 21 longest_palindrome = even_palindrome 22 23 # Return the longest palindrome found 24 return longest_palindrome 25 26 def isPalindrome(self, s, start, end): 27 # While the start and end indices are within the bounds of the string 28 # and the characters at these indices are equal, move the indices inward 29 while start \u0026gt;= 0 and end \u0026lt; len(s) and s[start] == s[end]: 30 start -= 1 31 end += 1 32 33 # Return the palindrome substring 34 # Note that start and end are now at the indices immediately 35 # before and after the palindrome, so we need to add 1 to start 36 # and subtract 1 from end to get the actual palindrome substring 37 return s[start+1 : end] The time complexity of the longestPalindrome() method is O(n^2), where n is the length of the input string s. This is because the method iterates through the string once to check for odd-length palindromes and once to check for even-length palindromes, and for each iteration it calls the isPalindrome() method, which has a time complexity of O(n).\nThe space complexity of the longestPalindrome() method is O(1), as it only stores a few constant-sized variables (e.g. longest_palindrome, odd_palindrome, and even_palindrome).\nThe time complexity of the isPalindrome() method is O(n), as it iterates through the string once while the indices start and end are within the bounds of the string and the characters at these indices are equal.\nThe space complexity of the isPalindrome() method is also O(1), as it only stores a few constant-sized variables (e.g. start and end).\nPalindromic Substrings Given a string s, return the number of palindromic substrings in it. A string is a palindrome when it reads the same backward as forward.\nA substring is a contiguous sequence of characters within the string.\nExample 1:\n1Input: s = \u0026#34;abc\u0026#34; 2Output: 3 3Explanation: Three palindromic strings: \u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;. Example 2:\n1Input: s = \u0026#34;aaa\u0026#34; 2Output: 6 3Explanation: Six palindromic strings: \u0026#34;a\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;aa\u0026#34;, \u0026#34;aa\u0026#34;, \u0026#34;aaa\u0026#34;. Brute Force\n1class Solution: 2 def countSubstrings(self, s: str) -\u0026gt; int: 3 count = 0 4 n = len(s) 5 6 # create all possible substrings 7 for i in range(n): 8 for j in range(i, n): 9 # get the substring 10 substring = s[i: j+1] 11 # check if the substring is palindrome 12 # if it\u0026#39;s palindrome, then increase the count by 1 13 if self.is_palindrome(substring): 14 count += 1 15 return count 16 17 18\t# helper function to check if a string is palindrome or not 19 def is_palindrome(self, s): 20 return s == s[::-1] Time complexity: O(n^3), where n is the length of the input string. This is because we iterate over all possible substrings using two nested loops and for each substring, we check if it is a palindrome using the isPalindrome function which takes O(n) time.\nSpace Complexity: O(1) because we only use a constant amount of extra space to store the count and temporary substrings.\nOptimized Solution\n1class Solution: 2 def countSubstrings(self, s: str) -\u0026gt; int: 3 # Initialize a counter to keep track of the number of palindromes found 4 palindrome_count = 0 5 6 # Iterate through each character in the input string 7 for i in range(len(s)): 8 # Check for palindromes with odd length (e.g. \u0026#34;aba\u0026#34;) 9 palindrome_count += self.expandFromCenter(s, i, i) 10 # Check for palindromes with even length (e.g. \u0026#34;abba\u0026#34;) 11 palindrome_count += self.expandFromCenter(s, i, i+1) 12 13 # Return the total number of palindromic substrings 14 return palindrome_count 15 16 def expandFromCenter(self, s, left, right): 17 # Initialize a counter to keep track of the number of palindromes 18 # found while expanding from the center 19 count = 0 20 21 # While the indices are within the bounds of the string 22 # and the characters at these indices are equal, 23 # increment the counter and move the indices outward 24 while left \u0026gt;= 0 and right \u0026lt; len(s) and s[left] == s[right]: 25 count += 1 26 left -= 1 27 right += 1 28 29 # Return the number of palindromes found 30 return count This solution has a time complexity of O(n^2), as it iterates through the string once to check every possible palindrome substring, and each palindrome check requires another iteration through the string. Its space complexity is O(1), as it does not allocate any additional space beyond a few variables.\nUnique Paths There is a robot on an m x n grid. The robot is initially located at the top-left corner (i.e., grid[0][0]). The robot tries to move to the bottom-right corner (i.e., grid[m - 1][n - 1]). The robot can only move either down or right at any point in time.\nGiven the two integers m and n, return the number of possible unique paths that the robot can take to reach the bottom-right corner.\nThe test cases are generated so that the answer will be less than or equal to 2 * 109.\nExample 1:\n1Input: m = 3, n = 7 2Output: 28 Example 2:\n1Input: m = 3, n = 2 2Output: 3 3Explanation: From the top-left corner, there are a total of 3 ways to reach the bottom-right corner: 41. Right -\u0026gt; Down -\u0026gt; Down 52. Down -\u0026gt; Down -\u0026gt; Right 63. Down -\u0026gt; Right -\u0026gt; Down Solution 1\n1from functools import lru_cache 2# recursion to explore all possible paths from the top-left corner to the bottom-right corner 3class Solution: 4 def uniquePaths(self, m: int, n: int) -\u0026gt; int: 5 @lru_cache(maxsize=None) 6 # current row i and column j 7 def backtrack(i, j): 8 # Base case: reached the bottom-right corner 9 if i == m - 1 and j == n - 1: 10 return 1 11 12 # Base case: out of bounds, return 0 13 if i \u0026gt;= m or j \u0026gt;= n: 14 return 0 15 16 # Recursive case: move down and right 17 # down: i + 1, j; increase row index 18 # right: i, j + 1; increase column index 19 return backtrack(i + 1, j) + backtrack(i, j + 1) 20 21 return backtrack(0, 0) Time complexity: O(m _ n) because, with the LRU cache, each unique input to the backtrack function is computed only once. The number of unique inputs to the function is equal to the number of cells in the grid, which is m _ n.\nSpace complexity: O(m _ n) because of the LRU cache. The cache stores the computed results, and since there are m _ n unique inputs, the cache size will be proportional to that.\nLongest Consecutive Sequence Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence.\nYou must write an algorithm that runs in O(n) time.\nExample 1:\n1Input: nums = [100,4,200,1,3,2] 2Output: 4 3Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4. Example 2:\n1Input: nums = [0,3,7,2,5,8,4,6,0,1] 2Output: 9 1class Solution: 2 def longestConsecutive(self, nums: List[int]) -\u0026gt; int: 3 # Create a set to store the elements in the array 4 num_set = set(nums) 5 longest_seq = 0 6 7 # Iterate through the array 8 for num in nums: 9 # Check if this element is the start of a consecutive sequence 10 if num - 1 not in num_set: 11 # Find the length of the consecutive sequence starting from this element 12 cur_seq = 1 13 while num + cur_seq in num_set: 14 cur_seq += 1 15 # Update the longest consecutive sequence length if necessary 16 longest_seq = max(longest_seq, cur_seq) 17 18 return longest_seq This algorithm has a time complexity of O(n), since we only iterate through the array once and all other operations (adding to a set and checking if an element is in a set) have a time complexity of O(1).\nMaximum Depth of Binary Tree Given the root of a binary tree, return its maximum depth.\nA binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.\nExample 1:\n1Input: root = [3,9,20,null,null,15,7] 2Output: 3 Example 2:\n1Input: root = [1,null,2] 2Output: 2 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8class Solution: 9 def maxDepth(self, root: Optional[TreeNode]) -\u0026gt; int: 10 # Base case: if the root is None, return 0 11 if not root: 12 return 0 13 14 # Recursively find the maximum depth of the left and right subtrees 15 left_depth = self.maxDepth(root.left) 16 right_depth = self.maxDepth(root.right) 17 18 # Return the maximum depth of the left and right subtrees, plus 1 for the root node 19 return max(left_depth, right_depth) + 1 The time complexity of the above code is O(n), where n is the number of nodes in the binary tree. This is because the function is called once for each node in the tree, and the work done in each call is constant (O(1)).\nThe space complexity is also O(n), since the maximum size of the call stack will be n when the tree is a degenerate tree (i.e., a tree that is a linked list). This is because at each level of the tree, we need to store a function call on the call stack. In the worst case, the tree is a linked list, so we will have n function calls on the call stack.\nSame Tree Given the roots of two binary trees p and q, write a function to check if they are the same or not.\nTwo binary trees are considered the same if they are structurally identical, and the nodes have the same value.\nExample 1:\n1Input: p = [1,2,3], q = [1,2,3] 2Output: true Example 2:\n1Input: p = [1,2], q = [1,null,2] 2Output: false 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8class Solution: 9 def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -\u0026gt; bool: 10 # If both nodes are None, return True 11 if not p and not q: 12 return True 13 14 # If one of the nodes is None but the other is not, return False 15 if not p or not q: 16 return False 17 18 # If the values of the nodes are not equal, return False 19 if p.val != q.val: 20 return False 21 22 # Recursively check if the left and right subtrees are the same 23 return ( 24 self.isSameTree(p.left, q.left) and 25 self.isSameTree(p.right, q.right) 26 ) The time complexity of the above code is O(n), where n is the number of nodes in the binary tree. This is because the function is called once for each node in the tree, and the work done in each call is constant (O(1)).\nThe space complexity is also O(n), since the maximum size of the call stack will be n when the tree is a degenerate tree (i.e., a tree that is a linked list). This is because at each level of the tree, we need to store a function call on the call stack. In the worst case, the tree is a linked list, so we will have n function calls on the call stack.\nInvert Binary Tree Given the root of a binary tree, invert the tree, and return its root.\n1Input: root = [4,2,7,1,3,6,9] 2Output: [4,7,2,9,6,3,1] 1# Definition for a binary tree node. 2# class TreeNode: 3# def __init__(self, val=0, left=None, right=None): 4# self.val = val 5# self.left = left 6# self.right = right 7 8 9class Solution: 10 def invertTree(self, root: Optional[TreeNode]) -\u0026gt; Optional[TreeNode]: 11 12 if root: 13 root.left, root.right = self.invertTree(root.right), self.invertTree(root.left) 14 15 return root The time complexity of the above code is O(n), where n is the number of nodes in the binary tree. This is because the invertTree function is called once for each node in the tree, and the work done in each call is constant (O(1)).\nThe space complexity is also O(n), since the maximum size of the call stack will be n when the tree is a degenerate tree (i.e., a tree that is a linked list). This is because at each level of the tree, we need to store a function call on the call stack. In the worst case, the tree is a linked list, so we will have n function calls on the call stack.\nBinary Tree Level Order Traversal Given the root of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level).\n1Input: root = [3,9,20,null,null,15,7] 2Output: [[3],[9,20],[15,7]] 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8# This code performs a breadth-first traversal of a binary tree and returns a list of lists, 9# where each inner list represents the values at a particular level of the tree. 10 11 12class Solution: 13 def levelOrder(self, root: Optional[TreeNode]) -\u0026gt; List[List[int]]: 14 # Initialize an empty queue and two empty lists to store the result and the current level 15 queue = [] 16 result = [] 17 level = [] 18 19 # If the root node is not None, add it to the queue 20 if root: 21 queue.append(root) 22 23 # While the queue is not empty 24 while queue: 25 # For each node in the queue (i.e., for each node in the current level) 26 for _ in range(len(queue)): 27 # Remove the first node from the queue and add its value to the current level 28 node = queue.pop(0) 29 level.append(node.val) 30 31 # If the node has a left child, add it to the queue 32 if node.left: 33 queue.append(node.left) 34 35 # If the node has a right child, add it to the queue 36 if node.right: 37 queue.append(node.right) 38 39 # Add the current level to the result 40 result.append(level) 41 42 # Reset the current level 43 level = [] 44 45 # Return the result 46 return result The time complexity of this code is O(n), where n is the number of nodes in the binary tree. This is because the function is called once for each node in the tree, and the work done in each call is constant (O(1)).\nThe space complexity is also O(n), since the maximum size of the call stack will be n when the tree is a degenerate tree (i.e., a tree that is a linked list). This is because at each level of the tree, we need to store a function call on the call stack.\nBinary Tree Maximum Path Sum Given the root of a binary tree, return the maximum path sum of any non-empty path.\nExample 1:\n1Input: root = [1,2,3] 2Output: 6 3Explanation: The optimal path is 2 -\u0026gt; 1 -\u0026gt; 3 with a path sum of 2 + 1 + 3 = 6. Example 2:\n1Input: root = [-10,9,20,null,null,15,7] 2Output: 42 3Explanation: The optimal path is 15 -\u0026gt; 20 -\u0026gt; 7 with a path sum of 15 + 20 + 7 = 42. 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8 9class Solution: 10 def maxPathSum(self, root: TreeNode) -\u0026gt; int: 11 # Initialize the maximum path sum to the minimum possible value 12 max_sum = float(\u0026#39;-inf\u0026#39;) 13 14 def helper(node): 15 nonlocal max_sum 16 17 # If the node is None, return 0 18 if not node: 19 return 0 20 21 # Recursively find the maximum path sum of the left and right subtrees 22 left_sum = helper(node.left) 23 right_sum = helper(node.right) 24 25 # Update the maximum path sum with the current node 26 # and the maximum path sum of the left and right subtrees 27 max_sum = max(max_sum, node.val + left_sum + right_sum) 28 29 # Return the maximum path sum that includes the current node 30 # and either the left or right subtree 31 return max(node.val + left_sum, node.val + right_sum, 0) 32 33 # Find the maximum path sum using the helper function 34 helper(root) 35 36 # Return the maximum path sum 37 return max_sum 38 39 40if __name__ == \u0026#39;__main__\u0026#39;: 41 # Test the solution 42 root = TreeNode(1) 43 root.left = TreeNode(2) 44 root.right = TreeNode(3) 45 print(Solution().maxPathSum(root)) # Output: 6 Time \u0026amp; Space complexity:\n$$ O(n) $$\nSubtree of Another Tree Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.\nA subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself.\nExample 1:\n1Input: root = [3,4,5,1,2], subRoot = [4,1,2] 2Output: true Example 2:\n1Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2] 2Output: false 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8 9class Solution: 10 def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -\u0026gt; bool: 11 # if the subRoot is empty, then it is a subtree of root 12 # return True 13 if subRoot is None: 14 return True 15 16 if root is None: 17 return False 18 19 # if the root and subRoot are the same tree, then return True 20 if self.isSameTree(root, subRoot): 21 return True 22 23 # if the root and subRoot are not the same tree, then check the left and right subtree 24 return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot) 25 26 27 def isSameTree(self, p, q): 28 if p is None and q is None: 29 return True 30 if p is None or q is None: 31 return False 32 if p.val != q.val: 33 return False 34 # when same tree, return True, else return False 35 return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) The time complexity of the isSubtree function is O(n^2) in the worst case, where n is the number of nodes in the root tree. This is because in the worst case, the function will need to traverse the entire root tree and for each node, it will also need to traverse the entire subRoot tree to check if they are the same tree.\nThe space complexity of the isSubtree function is O(n) in the worst case, where n is the number of nodes in the root tree. This is because in the worst case, the function will need to store all the nodes in the root tree in the call stack during the recursive calls.\nThe time complexity of the isSameTree function is O(n), where n is the number of nodes in the p tree. This is because in the worst case, the function will need to traverse the entire p tree to check if it is the same as the q tree.\nThe space complexity of the isSameTree function is O(n) in the worst case, where n is the number of nodes in the p tree. This is because in the worst case, the function will need to store all the nodes in the p tree in the call stack during the recursive calls.\nConstruct Binary Tree from Preorder and Inorder Traversal Given two integer arrays preorder and inorder where preorder is the preorder traversal of a binary tree and inorder is the inorder traversal of the same tree, construct and return the binary tree.\n1Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 2Output: [3,9,20,null,null,15,7] 1# Definition for a binary tree node. 2 class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8 9class Solution: 10 def buildTree(self, preorder: List[int], inorder: List[int]) -\u0026gt; Optional[TreeNode]: 11 # if either preorder or inorder is empty, return None 12 if not preorder or not inorder: 13 return None 14 15 # get the root node from the preorder list and find its index in the inorder list 16 root_val = preorder.pop(0) 17 root_index = inorder.index(root_val) 18 19 # create the root node using the value from the inorder list 20 root = TreeNode(inorder[root_index]) 21 22 # recursively build the left and right subtrees using the elements before and after the root node in the inorder list 23 root.left = self.buildTree(preorder, inorder[0: root_index]) 24 root.right = self.buildTree(preorder, inorder[root_index + 1: ]) 25 26 return root The time complexity of the buildTree function is O(n^2) in the worst case, where n is the number of nodes in the binary tree. This is because in the worst case, the function will need to traverse the entire preorder and inorder lists for each recursive call.\nThe space complexity of the buildTree function is O(n) in the worst case, where n is the number of nodes in the binary tree. This is because in the worst case, the function will need to store all the nodes in the binary tree in the call stack during the recursive calls.\nValidate Binary Search Tree Given the root of a binary tree, determine if it is a valid binary search tree (BST).\nA valid BST is defined as follows:\nThe left subtree of a node contains only nodes with keys less than the node's key. The right subtree of a node contains only nodes with keys greater than the node's key. Both the left and right subtrees must also be binary search trees. Example 1:\n1Input: root = [2,1,3] 2Output: true Example 2:\n1Input: root = [5,1,4,null,null,3,6] 2Output: false 3Explanation: The root node\u0026#39;s value is 5 but its right child\u0026#39;s value is 4. 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8class Solution: 9 def isValidBST(self, root: Optional[TreeNode]) -\u0026gt; bool: 10 \u0026#34;\u0026#34;\u0026#34; 11 A BST is a binary tree in which the values of the left and right 12 subtrees of every node are strictly less than and greater than, 13 respectively, the value of the node. 14 15 \u0026#34;\u0026#34;\u0026#34; 16 # get the inorder traversal of the tree 17 output = self.inorder(root) 18 19 # check if the values in the inorder traversal are in ascending order 20 for i in range(1, len(output)): 21 if output[i] \u0026lt;= output[i-1]: 22 return False 23 24 return True 25 26 27 def inorder(self, root): 28 \u0026#34;\u0026#34;\u0026#34; 29 Get the inorder traversal of a binary tree. 30 \u0026#34;\u0026#34;\u0026#34; 31 # if the root is None, return an empty list 32 if not root: 33 return [] 34 35 # get the inorder traversal of the left subtree, append the root value, 36 # and then append the inorder traversal of the right subtree 37 return self.inorder(root.left) + [root.val] + self.inorder(root.right) The time complexity of the isValidBST function is O(n), where n is the number of nodes in the binary tree. This is because the function needs to traverse the entire tree to get the inorder traversal and then check if the values in the traversal are in ascending order.\nThe space complexity of the isValidBST function is O(n) in the worst case, where n is the number of nodes in the binary tree. This is because in the worst case, the function will need to store all the values in the inorder traversal in a list.\nThe time complexity of the inorder function is O(n), where n is the number of nodes in the binary tree. This is because the function needs to traverse the entire tree to get the inorder traversal.\nThe space complexity of the inorder function is O(n) in the worst case, where n is the number of nodes in the binary tree. This is because in the worst case, the function will need to store all the nodes in the binary tree in the call stack during the recursive calls.\nKth Smallest Element in a BST Given the root of a binary search tree, and an integer k, return the kth smallest value (1-indexed) of all the values of the nodes in the tree.\nExample 1:\n1Input: root = [3,1,4,null,2], k = 1 2Output: 1 Example 2:\n1Input: root = [5,3,6,2,4,null,null,1], k = 3 2Output: 3 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, val=0, left=None, right=None): 4 self.val = val 5 self.left = left 6 self.right = right 7 8class Solution: 9 def kthSmallest(self, root: Optional[TreeNode], k: int) -\u0026gt; int: 10 # get the inorder traversal of the BST 11 output = self.inorder(root) 12 13 # sort the list in ascending order 14 output.sort() 15 16 # return the kth element from the sorted list 17 return output[k-1] # -1 because it\u0026#39;s 1 indexed 18 19 20 def inorder(self, root): 21 \u0026#34;\u0026#34;\u0026#34; 22 Get the inorder traversal of a binary tree. 23 \u0026#34;\u0026#34;\u0026#34; 24 # if the root is None, return an empty list 25 if not root: 26 return [] 27 28 # get the inorder traversal of the left subtree, append the root value, 29 # and then append the inorder traversal of the right subtree 30 return self.inorder(root.left) + [root.val] + self.inorder(root.right) The time complexity of the kthSmallest function is O(n log n), where n is the number of nodes in the BST. This is because the function needs to traverse the entire BST to get the inorder traversal and then sort the list in ascending order.\nThe space complexity of the kthSmallest function is O(n) in the worst case, where n is the number of nodes in the BST. This is because in the worst case, the function will need to store all the values in the inorder traversal in a list.\nThe time complexity of the inorder function is O(n), where n is the number of nodes in the binary tree. This is because the function needs to traverse the entire tree to get the inorder traversal.\nThe space complexity of the inorder function is O(n) in the worst case, where n is the number of nodes in the binary tree. This is because in the worst case, the function will need to store all the nodes in the binary tree in the call stack during the recursive calls.\nLowest Common Ancestor of a Binary Search Tree Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.\nAccording to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”\nExample 1:\n1Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 2Output: 6 3Explanation: The LCA of nodes 2 and 8 is 6. Example 2:\n1Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 2Output: 2 3Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. 1# Definition for a binary tree node. 2class TreeNode: 3 def __init__(self, x): 4 self.val = x 5 self.left = None 6 self.right = None 7 8class Solution: 9 def lowestCommonAncestor(self, root: \u0026#39;TreeNode\u0026#39;, p: \u0026#39;TreeNode\u0026#39;, q: \u0026#39;TreeNode\u0026#39;) -\u0026gt; \u0026#39;TreeNode\u0026#39;: 10 # if p and q are both smaller than root, they are in the left subtree 11 if p.val \u0026lt; root.val and q.val \u0026lt; root.val: 12 return self.lowestCommonAncestor(root.left, p, q) 13 14 # if p and q are both greater than root, they are in the right subtree 15 if p.val \u0026gt; root.val and q.val \u0026gt; root.val: 16 return self.lowestCommonAncestor(root.right, p, q) 17 18 # otherwise, they are on different sides (or equal) and the root is LCA 19 return root Implement Trie (Prefix Tree) A trie (pronounced as \u0026quot;try\u0026quot;) or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.\nImplement the Trie class:\nTrie() Initializes the trie object. void insert(String word) Inserts the string word into the trie. boolean search(String word) Returns true if the string word is in the trie (i.e., was inserted before), and false otherwise. boolean startsWith(String prefix) Returns true if there is a previously inserted string word that has the prefix prefix, and false otherwise. 1Input 2[\u0026#34;Trie\u0026#34;, \u0026#34;insert\u0026#34;, \u0026#34;search\u0026#34;, \u0026#34;search\u0026#34;, \u0026#34;startsWith\u0026#34;, \u0026#34;insert\u0026#34;, \u0026#34;search\u0026#34;] 3[[], [\u0026#34;apple\u0026#34;], [\u0026#34;apple\u0026#34;], [\u0026#34;app\u0026#34;], [\u0026#34;app\u0026#34;], [\u0026#34;app\u0026#34;], [\u0026#34;app\u0026#34;]] 4Output 5[null, null, true, false, true, null, true] 6 7Explanation 8Trie trie = new Trie(); 9trie.insert(\u0026#34;apple\u0026#34;); 10trie.search(\u0026#34;apple\u0026#34;); // return True 11trie.search(\u0026#34;app\u0026#34;); // return False 12trie.startsWith(\u0026#34;app\u0026#34;); // return True 13trie.insert(\u0026#34;app\u0026#34;); 14trie.search(\u0026#34;app\u0026#34;); // return True 1class Trie: 2 def __init__(self): 3 # Initialize an empty dictionary as the root of the Trie 4 self.root = {} 5 6 def insert(self, word: str) -\u0026gt; None: 7 # Start at the root node 8 node = self.root 9 10 # Iterate through each character in the word 11 for c in word: 12 # If the character is not already a key in the current node, 13 # add it as a key and set its value to an empty dictionary 14 node = node.setdefault(c, {}) 15 16 # Add the special character \u0026#39;#\u0026#39; to mark the end of the word 17 node[\u0026#39;#\u0026#39;] = \u0026#39;#\u0026#39; 18 19 def search(self, word: str) -\u0026gt; bool: 20 # Start at the root node 21 node = self.root 22 23 # Iterate through each character in the word 24 for c in word: 25 # If the character is not a key in the current node, 26 # return False as the word is not present in the Trie 27 if c not in node: 28 return False 29 # Otherwise, move to the next node in the Trie 30 node = node[c] 31 32 # Return True if the special character \u0026#39;#\u0026#39; is present in the current node, 33 # indicating that the word is present in the Trie 34 return \u0026#39;#\u0026#39; in node 35 36 37 def startsWith(self, prefix: str) -\u0026gt; bool: 38 # Start at the root node 39 node = self.root 40 41 # Iterate through each character in the prefix 42 for c in prefix: 43 # If the character is not a key in the current node, 44 # return False as the prefix is not present in the Trie 45 if c not in node: 46 return False 47 # Otherwise, move to the next node in the Trie 48 node = node[c] 49 50 # Return True as the prefix is present in the Trie 51 return True Bonus Problems 3Sum Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.\nNotice that the solution set must not contain duplicate triplets.\n1Input: nums = [-1,0,1,2,-1,-4] 2Output: [[-1,-1,2],[-1,0,1]] 3Explanation: 4nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. 5nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. 6nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. 7The distinct triplets are [-1,0,1] and [-1,-1,2]. 8Notice that the order of the output and the order of the triplets does not matter. Brute Force\n1# generate all possible triplets from the input array, and check if their sum is zero 2class Solution: 3 def threeSum(self, nums: List[int]) -\u0026gt; List[List[int]]: 4 # use set to avoid duplicates 5 res = set() 6 7 n = len(nums) 8 # generate all possible triplets 9 for i in range(n): 10 for j in range(i+1, n): 11 for k in range(j+1, n): 12 # check if their sum equals to zero 13 if nums[i] + nums[j] + nums[k] == 0: 14 # here we will use tuple, because we can not add lists in the set 15 # lists are mutable, they cannot be added to a set since the elements 16 # of a set need to be immutable and tuple are immutable. 17 # sort the triplet to ensure that the same triplet will not be added 18 # more than once, regardless of order 19 res.add(tuple(sorted([nums[i], nums[j], nums[k]]))) 20 # iterate over each element of the set and convert each element to a list 21 # as the output expects lists of list 22 return [list(x) for x in res] Time Complexity: O(n^3), where n is the number of elements in the input list. Space Complexity: O(n), where n is the number of unique triplets that sum up to zero.\nOptimized Solution\n1# optimized solution for this problem involves sorting the list 2# and then using a two-pointer technique 3class Solution: 4 def threeSum(self, nums: List[int]) -\u0026gt; List[List[int]]: 5 # initialize result as a set to avoid duplicates 6 res = set() 7 nums.sort() 8 for i in range(len(nums) - 2): 9 # skip the same number to avoid duplicate triplets 10 # check i \u0026gt; 0 because, at 0, there is no i-1, which will cause error 11 if i \u0026gt; 0 and nums[i] == nums[i - 1]: 12 continue 13 # initialize two pointers 14 l, r = i + 1, len(nums) - 1 15 while l \u0026lt; r: 16 # calculate sum of the 3 numbers 17 s = nums[i] + nums[l] + nums[r] 18 # if the sum is less than 0, increase the left pointer to increase the sum 19 if s \u0026lt; 0: 20 l += 1 21 # if the sum is more than 0, decrease the right pointer to decrease the sum 22 elif s \u0026gt; 0: 23 r -= 1 24 else: 25 # if the sum is 0, we have found a triplet; add it to the result 26 res.add((nums[i], nums[l], nums[r])) 27 # move both pointers inward to continue searching for other possible triplets 28 l += 1 29 r -= 1 30 # convert each tuple in the set back to a list 31 return [list(x) for x in res] Time Complexity: O(n^2), where n is the number of elements in the input list. This is because we have a single loop running n times, and inside this loop, we're potentially moving two pointers across the array in the worst-case scenario. The sorting operation at the beginning also takes O(n log n) time, but O(n^2) dominates O(n log n) so we say the time complexity is O(n^2). Space Complexity: O(n), where n is the number of unique triplets that sum up to zero.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/optimized-python-solutions-blind-75-leetcode/","section":"software-engineering","tags":["algorithms","leetcode","problem solving"],"title":"Brute-Force to Optimized Python Solutions for All LeetCode Blind-75 Problems"},{"body":"","link":"https://blog.sksoumik.com/tags/leetcode/","section":"tags","tags":null,"title":"leetcode"},{"body":"","link":"https://blog.sksoumik.com/tags/problem-solving/","section":"tags","tags":null,"title":"problem solving"},{"body":"","link":"https://blog.sksoumik.com/series/problem-solving/","section":"series","tags":null,"title":"problem solving"},{"body":"","link":"https://blog.sksoumik.com/software-engineering/","section":"software-engineering","tags":null,"title":"Software-engineerings"},{"body":"","link":"https://blog.sksoumik.com/tags/airflow/","section":"tags","tags":null,"title":"Airflow"},{"body":"","link":"https://blog.sksoumik.com/series/airflow/","section":"series","tags":null,"title":"Airflow"},{"body":" Author: Sadman Kabir Soumik\nLet's first understand what's MLOps.\nWhat is MLOps? MLOps (Machine Learning Operations) is a set of practices and tools used to manage the entire lifecycle of machine learning models. MLOps includes everything from data preparation and model training to deployment, monitoring, and ongoing maintenance.\nThe primary goal of MLOps is to create a streamlined and automated process for deploying and managing machine learning models at scale. This requires collaboration between data scientists, software engineers, operations teams, and advanced tools and technologies.\nSome of the key components of MLOps include:\nVersion control: Tracking changes to the code and models over time, enabling reproducibility and collaboration between team members.\nContinuous integration and delivery (CI/CD): Automating the process of testing, building, and deploying models to production environments, reducing the risk of errors and downtime.\nInfrastructure as code (IaC): Treating infrastructure as software, enabling the automated and reproducible deployment of models and related infrastructure.\nMonitoring and alerting: Tracking model performance and detecting issues in real-time, enabling proactive maintenance and optimization.\nGovernance and compliance: Ensuring that models comply with relevant regulations and ethical standards while enabling easy auditing and traceability.\nDifferent Components of Machine Learning Workflow To automate and manage the entire lifecycle of ML models, we need to understand what's the workflow of an ML product.\nLargely, we can divide the entire process in three parts:\nExperiment and develop Operationalize Orchestrate Experiment and Develop In the experiment and develop phase, we explore the data, perform feature engineering, select models, train and evaluate models, and tune hyperparameters to get the best model performance. We must keep track of all the trials and their outcomes because this phase involves a lot of experimentation.\nData featurization: The first step is to gather and transform data into a format the ML algorithm can understand. This technique is called data featurization. We must perform feature engineering to select relevant features, convert the data into numerical values, and handle missing data. Modeling: The next step is to select the appropriate ML model for the given problem. We can choose from several models, such as linear regression, logistic regression, decision trees, random forests, support vector machines, and neural networks. We need to select the best model based on the problem statement, the data, and the evaluation metrics. Training and evaluation: After selecting the model, we need to train it on the data and evaluate its performance. We can split the data into training, validation, and testing sets to evaluate the model's performance. We need to keep track of the training and evaluation metrics for each experiment. Operationalize In the operationalize phase, we need to design the ML architecture, scale it to handle large volumes of data, and ensure that it is reliable and secure.\nArchitecture: We need to design the ML architecture that meets the business requirements and integrates with other systems in the organization. We can choose from various architectures such as batch processing, real-time processing, microservices, and serverless. Scaling: We need to scale the ML architecture to handle large volumes of data and handle multiple requests in parallel. We can use horizontal scaling by adding more servers or vertical scaling by adding more resources to the existing server. Reliability: We need to ensure that the ML architecture is reliable and can handle failures gracefully. We need to design the architecture to be fault-tolerant and use monitoring and alerting to detect and resolve issues. Orchestrate In the orchestrate phase, we need to automate the entire ML workflow from model training to deployment and monitor the deployed models' performance.\nScheduling: We need to schedule the ML workflows to run at regular intervals, such as daily, weekly, or monthly. We can use workflow orchestration tools such as Apache Airflow to schedule and manage the workflows. Versioning and serving: We need to version the ML models to keep track of changes and deploy the latest version of the model. We can use containerization tools such as Docker to package the ML models and deploy them to production. Monitoring and Governance: We need to monitor the performance of the deployed models and ensure that they meet the business requirements. We can use monitoring and governance tools to detect and resolve issues, ensure compliance with regulations, and maintain the model's fairness and accuracy. Steps to Develop an End-to-End Machine Learning Project Developing an AI/ML application involves several steps:\nClarify the business requirements Assess available data Develop data science pipeline Deploy model Maintain the model operations So, if we think of this as an ML application lifecycle, how do we actually achieve it?\nThere are numerous ways in which it's currently being achieved by various teams at various institutions. There is no standard or hard core rule to achieve this lifecycle. It depends on your needs. Few of the tools that people use heavily to achieve this lifecycle are listed below:\nProductionized Notebooks MLOps Platforms Custom Pipelines AWS SageMaker Kubeflow Python/R GCP Vertex AI Notebooks AWS SageMaker SQL Databricks Azure ML Apache Airflow Papermil Vertex AI Metaflow Kedro Git H2O Circle CI Typical End-to-End ML Pipeline This is again going into the different steps of end-to-end ML pipelines. We start with Data Ingestion which falls on Data Engineering. Then we have Data Exploration, Feature Engineering, and Model Building, a more experimental phase that falls under Data Science. Once the model is trained, we move to the MLOps aspect, where we define how the model will be retrained, how often the model is going to be retrained, how to deploy/serve the model at scale, and monitoring the model performance in production.\nAt each of these stages, we are going to work with our business counterparts to define the requirements and make sure that our product is adding value to the users.\nNow, which of the above steps are automatable?\nExploration and Experimentation in Data Science part is not usually automatable because we need continuous research to improve these steps. However, rest of the part is automatable.\nIn fact, we can use the the Monitoring to trigger the pipeline to circle back to the Data Science phase of feature engineering and model optimization, when a model is not performing well. So, the automated production pipeline includes ingestion, featurization, model training, deployment, and monitoring, all of which can be accomplished using the Python-native tool Airflow.\nAutomating with Airflow So, why should we use Airflow?\nAirflow is a Python-native tool that data scientists and ML engineers commonly use due to its integration with many other tools such as TensorFlow, SageMaker, MLflow, and Spark. Airflow has great features for logging, monitoring, and alerting and is extensible, allowing the writing of custom operators. It is also pluggable for compute, elastic, data-aware, and cloud-neutral. The automated parts of the pipeline can be scheduled, and dependencies can be set using sensors or by directly triggering one part from another. The text notes that one can write separate dags for each pipeline stage, such as training, validation, deployment, and prediction. The metadata and data can be passed between tasks to enable communication and agreement on inputs and outputs.\nDAG - Data Structure of Airflow DAG stands for Directed Acyclic Graph in Airflow. In Airflow, A DAG in Airflow is a group of tasks with dependencies on one another that are shown as nodes in a graph. Tasks are represented as nodes in the graph, and their dependencies are shown as edges. The graph is directed, meaning that the edges point from upstream tasks to downstream tasks, indicating that the downstream tasks depend on the upstream tasks to be completed first.\nThe graph is also acyclic, meaning there are no circular dependencies, i.e., no tasks depend on themselves or each other in a circular manner. This ensures that the tasks can be executed in a logical order without any infinite loops. Airflow uses the DAG concept to schedule and execute workflows consisting of multiple tasks. The user defines a DAG in Python code, including the tasks and their dependencies. Airflow manages the scheduling and execution of the tasks according to the dependencies defined in the DAG.\nIf you notice the above graph, Task B dependent on Task A, Task D dependent on Task B and Task C, and so on. It's an acyclic graph, which means there is no cycle. These types of graphs can be executed through Airflow. If there were circular dependencies, then Airflow couldn't execute those tasks.\nThis is the first part of this tutorial. Here, we explained the theoretical aspects of designing MLOps pipelines and why we can consider Apache Airflow for orchestrating our pipeline. In the second part of this tutorial, I will demonstrate how to develop an MLOps pipeline using Airflow code samples.\nAuthor: Sadman Kabir Soumik\nReferences:\n[1] https://airflow.apache.org/docs/apache-airflow/stable/\n[2] https://youtu.be/xS2wTgcWE3k\n[3] https://youtu.be/IH1-0hwFZRQ\n","link":"https://blog.sksoumik.com/artificial-intelligence/airflow-machine-learning-pipeline/","section":"artificial-intelligence","tags":["machine learning","MLOps","Airflow"],"title":"Building an MLOps Pipeline with Apache Airflow (Part 1)"},{"body":"","link":"https://blog.sksoumik.com/tags/mlops/","section":"tags","tags":null,"title":"MLOps"},{"body":"","link":"https://blog.sksoumik.com/tags/algorithm/","section":"tags","tags":null,"title":"algorithm"},{"body":"Transformer-based models are a types of neural network architecture that uses self-attention mechanisms to process input data. They were introduced in the paper \u0026quot;Attention Is All You Need\u0026quot; by Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin in 2017, and have since become a popular choice for many natural language processing task.\nPrerequisite: Before going further, I assume that you have a basic understanding of how neural networks work.\nBefore Transformers Before transformer models were developedd, one of the most commonly used types of models for natural language processing tasks was the recurrent neural network (RNN).\nSo, What's an RNN and How Does It Work? Let's look into a simple example.\nSuppose you are reading a story and you are trying to understand the meaning of the story. You start reading the first sentence and try to understand its meaning . you read the second sentence, and you try to understand its meaning. Finally, you read the third sentence, and you try to understand its meaning. Now, after reading the entire story, you can understand the meaning of the story.\nSimilarly, RNNs work in the same way. They take input one at a time, and they use the previous input to help them understand the current input. In other words, RNNs use their memory to remember the previous input and use it to help understand the current input.\nHow the RNN is different from normal neural networks? Normal neural networks are like calculators. They take some input and give you an output, but they don't remember anything from the previous calculations. RNNs are different because they have a memory that allows them to remember previous calculations and use that information to make better predictions or understand new information.\nNormal neural networks take fixed-sized inputs and produce fixed-sized outputs. In contrast, RNNs can take inputs of any size and produce outputs of any size. Moreover, RNNs can handle sequential data, such as time-series data or natural language data, which is challenging for normal neural networks.\nWhat are the different types of RNN? There are different types of RNN, like LSTM and GRU, which are designed to help solve some of the problems of the basic RNN.\nThe basic RNN suffers from the vanishing gradient problem, which means that the gradients can become very small or zero, making it difficult to train the network, which makes it difficult to learn long-term dependencies in sequential data. This means that when trying to make predictions or generate outputs based on long sequences of past inputs, the model may have difficulty remembering information from earlier in the sequence.\nLSTM and GRU are two types of RNN architectures designed to help alleviate the vanishing gradient problem by introducing mechanisms to selectively remember or forget information from previous inputs. This allows them to better capture long-term dependencies in sequential data.\nHow does LSTM is different from the Normal RNN? LSTM (Long Short-Term Memory) is a type of RNN (Recurrent Neural Network) that is designed to address the problem of vanishing gradients in traditional RNNs. Here are the key differences between LSTM and Normal RNN:\nMemory cells: In traditional RNNs, the hidden state at each timestep is used to pass information to the next timestep. However, in LSTMs, there is an additional memory cell that is used to pass information through time.\nGates: LSTMs have three gates that control the flow of information in and out of the memory cell.\nInput gate: Determines which new information to store in the cell state. Forget gate: Determines what information to discard from the cell state. Output gate: Determines what information to output from the cell state. Gradient flow: In traditional RNNs, the gradients can become very small as they are backpropagated through time, leading to the vanishing gradient problem. LSTMs address this problem by using the gates to selectively update the memory cell, which allows gradients to flow more easily through time.\nLonger-term dependencies: Because of the memory cell and gates, LSTMs are better able to capture longer-term dependencies in sequential data than traditional RNNs.\nComputationally more expensive: Because of the additional memory cell and gates, LSTMs are computationally more expensive than traditional RNNs.\nMain Difference Between LSTM and GRU The main difference between LSTM and GRU is that LSTM has two memory cells, while GRU has only one. LSTM is more complex and has more parameters than GRU, which makes it more powerful. However, GRU is simpler and faster to compute than LSTM.\nThe Basic Idea of Transformers The Transformer architecture was introduced by Google researchers in 2017. It was designed to address some of the limitations of the previous state-of-the-art neural network models for NLP tasks, like Recurrent Neural Networks (RNNs).\nThe Transformer architecture is a newer and more advanced technology that has several advantages over RNN, especially when it comes to processing long sequences of data.\nAs we have already discussed above RNN works by passing information from one node to the next in a sequence, with each node processing a small piece of information and passing it on to the next node. This creates a kind of \u0026quot;memory\u0026quot; that allows the network to remember what has come before and use that information to make predictions about what will come next.\nHowever, there are some limitations to RNN. One of the biggest is that it can struggle with processing very long sequences of data. This is because the network has to remember all of the previous information in the sequence, and as the sequence gets longer, the amount of information the network has to remember increases exponentially.\nThis is where the Transformer architecture comes in. It is designed to be much more efficient at processing long sequences of data, like entire paragraphs of text.\nThe Transformer architecture works by using a technique called \u0026quot;self-attention.\u0026quot; This is where the network focuses on certain parts of the input sequence and gives them more weight when processing the data. Think of it like a teacher focusing on a student who is struggling with a particular concept and giving them extra attention to help them understand it better.\nThis self-attention mechanism allows the Transformer architecture to process long sequences of data much more efficiently than RNN. It can also process multiple parts of the sequence at the same time, which makes it even faster and more efficient.\nOne other advantage of the Transformer architecture is that it can be pre-trained on large amounts of data to learn the structure of language. This means that when it is used for a specific language processing task, like translation or question-answering, it already has a good understanding of how language works and can perform the task more accurately.\nArchitecture of Transformer Imagine you are telling a story to your friends in class, and you want to make sure they understand every word you say. You might use a special notebook to write down all the words you want to use and their meanings, so you can remember them and explain them to your friends.\nThis notebook is like the \u0026quot;embedding layer\u0026quot; in the Transformer. It helps the computer understand the meaning of each word in a sentence by assigning each word a unique number or vector.\nNow, imagine you want to tell your story to someone who doesn't speak the same language as you. You might use a translator to help you. The translator listens to what you say, and then says the same thing in a language the other person can understand.\nThe translator is like the \u0026quot;encoder\u0026quot; in the Transformer. It listens to the words you say, and then converts them into a format the computer can understand. The encoder does this by breaking the sentence into smaller parts and analyzing how they relate to each other.\nFinally, imagine you want to write down your story in a different language, so people who speak that language can understand it. You might use a different notebook to write down the words in the new language and their meanings, so you can remember them and explain them to others.\nThis notebook is like the \u0026quot;decoder\u0026quot; in the Transformer. It takes the meaning of each word from the encoder and turns it into a sentence in a new language.\nSo, in short, the Transformer architecture is like a language translator. It uses an \u0026quot;embedding layer\u0026quot; to understand the meaning of words, an \u0026quot;encoder\u0026quot; to translate the words into a computer-readable format, and a \u0026quot;decoder\u0026quot; to turn the computer-readable format back into a sentence in a different language.\nTechnically, the Transformer architechture has two main components: the encoder and the decoder. The encoder processes the input data and the decoder generates the output.\nEncoder: The encoder takes in the input sequence, such as a sentence, and processes it into a set of \u0026quot;hidden\u0026quot; representations. These hidden representations capture the meaning of the input sequence and are used by the decoder to generate the output. The encoder is made up of several layers, each of which contains two sub-layers: Multi-head Attention Layer: This layer performs the self-attention mechanism that we talked about earlier. It allows the model to focus on different parts of the input sequence and give them varying degrees of importance. Feedforward Neural Network Layer: This layer applies a simple neural network to the output of the multi-head attention layer. It helps to further capture the meaning of the input sequence. Decoder: The decoder takes the hidden representations generated by the encoder and uses them to generate the output sequence, such as a translated sentence. The decoder is also made up of several layers, each of which contains three sub-layers: Masked Multi-head Attention Layer: This layer performs a similar function to the multi-head attention layer in the encoder, but it is \u0026quot;masked\u0026quot; so that the model can only attend to parts of the output sequence that have already been generated. Multi-head Attention Layer: This layer allows the model to attend to the hidden representations generated by the encoder. It helps the decoder to generate output sequences that are consistent with the input sequence. Feedforward Neural Network Layer: This layer applies a simple neural network to the output of the multi-head attention layer. It helps to further capture the meaning of the input sequence. Together, the encoder and decoder allow the Transformer model to process long sequences of data and generate accurate outputs. They also allow the model to be pre-trained on large amounts of data , which helps to improve its performance on specific language processing tasks.\nIn the Transformer architecture, \u0026quot;self-attention\u0026quot; refers to the mechanism by which the model determines which parts of the input sentence are most relevant to each other.\nLet me explain it in simpler terms. When we read a sentence we usually pay more attention to its certain words that help us understand the meaning of the sentence. For example, in this sentence \u0026quot;The cat sat on the mat\u0026quot;, We might pay more attention to the words \u0026quot;cat\u0026quot; and \u0026quot;mat\u0026quot; because they help us understand the subject and location of the sentence.\nIn the same way, the Transformer uses self-attention to determine which words in a sentence are most important for understanding the meaning of the sentence. It does this by computing a weighted sum of the input embeddings, where the weights are determined by how related each word is to the other words in the sentence.\nThe self-attention mechanism is called \u0026quot;self\u0026quot; attention because it pays attention to the input sentence itself, rather than any external information. It allows the models to focus on the most relevant part of the sentence, and ignore the less important ones, which can help improve the accuracy of the model's predictions.\nIn the Transformer architecture, self-attention is used in both the encoder and decoder components to determine which parts of the input sentence and output sentence are most relevant to each other. Specifically, the self-attention component is responsible for computing the attention weights and applying them to the input embeddings to obtain the final output embeddings.\nThere is an excellent blog named The Illustrated Transformer by Jay Alammar which explain the Transformer architecture in the simpliest way possible.\nWhy do transformer-based models work better than previous methods? Transformer based models have proven to be very effective for many natural language processing tasks, such as machine translation and language modeling, because they are able to capture long-term dependencies in the input data.\nPrevious methods, such as recurrent neural networks (RNNs), struggled to capture these long-term dependencies because they processed the input data in a sequential manner. This means that the output at each step was only dependent on the input at that step and the previous hidden state. In contrast, transformer-based models use self-attention mechanisms, which allow the model to look at the entire input sequence at once and weight the different parts of the input according to their importance. This makes it possible for the model to capture long-term dependencies and produce more accurate predictions.\nAdditionally, transformer-based models are highly parallelisable, which makes them much more efficient to train and run on modern hardware, such as GPUs and TPUs. This has allowed them to applied to large-scale natural language processing tasks, such as machine translation, which require the ability to process a large amount of data quickly.\nMajor Differences Between RNN and Transformers So, here are 5 major differences between RNN and Transformer models:\nRecurrent Neural Networks (RNN) Transformer Models Processing of input sequence Sequential processing, where the hidden state is updated based on the previous input and used to predict the next output Parallel processing, where each input is processed independently and the entire input sequence is used to generate the output Handling of long sequences Can struggle to handle long sequences due to \u0026quot;vanishing gradients\u0026quot; problem, where gradients become very small and the network cannot learn effectively Can effectively handle long sequences due to self-attention mechanism, which allows the model to attend to different parts of the sequence Memory of earlier inputs Hidden state can retain information from earlier inputs, but may struggle to retain information over long sequences Self-attention mechanism allows the model to retain information from all parts of the input sequence Training time Can be slow to train, especially for long sequences, due to sequential processing Can be faster to train than RNNs, especially for long sequences, due to parallel processing Applicability to different tasks Commonly used for sequential data tasks such as natural language processing, speech recognition, and time series analysis Primarily used for natural language processing tasks, but can also be used for image and video processing tasks Various Transformer-based Models The BERT (Bidirectional Encoder Representations from Transformers) model, which was developed by Google and is a popular choice for many natural language understanding tasks. The GPT (Generative Pre-trained Transformer) model, which was developed by OpenAI and is a popular choice for many natural language generation tasks. The Transformer-XL model, which was developed by Google and is a variant of the original transformer model that is able to capture longer-term dependencies in the input data. The XLNet model, which was developed by Google and is a variant of the Transformer-XL model that is able to capture even longer-term dependencies in the input data. The RoBERTa (Robustly Optimized BERT) model, which was developed by Facebook and is a variant of the BERT model that is trained on a larger dataset and uses a different training objective. The ALBERT (A Lite BERT) model, which was developed by Google and is a smaller and more efficient variant of the BERT model. The T5 (Text-To-Text Transfer Transformer) model, which was developed by Google and is a multi-task model that can be fine-tuned for a wide range of natural language processing tasks. The BART (Denoising Autoencoding Representations from Transformers) model, which was developed by Facebook and is a denoising autoencoder that is trained to reconstruct the input data from corrupted versions of it. The ELECTRA (Efficiently Learning an Encoder that Classifies Tokens Accurately) model, which was developed by Google and is a generative model that is trained to produce high-quality text. Author: Sadman Kabir Soumik\nReferences:\n[1] http://jalammar.github.io/illustrated-transformer/\n[2] https://arxiv.org/abs/1706.03762\n[3] https://youtu.be/kCc8FmEb1nY\n","link":"https://blog.sksoumik.com/artificial-intelligence/from-rnn-to-transformers-without-math/","section":"artificial-intelligence","tags":["machine learning","data science","NLP","algorithm"],"title":"From RNN to Transformers (Without Math Jargon)"},{"body":"","link":"https://blog.sksoumik.com/series/nlp/","section":"series","tags":null,"title":"NLP"},{"body":"","link":"https://blog.sksoumik.com/tags/computer-vision/","section":"tags","tags":null,"title":"computer vision"},{"body":"Project Goal The project aims to perform image segmentation on selfie images, see how we can blur the image's background, and even replace the background with some other solid colour like black.\nWe will use a framework called MediaPipe to accomplish this task.\nAbout MediaPipe MediaPipe is an open-source framework developed by Google that allows developers to build and deploy cross-platform multimodal machine learning models. The framework provides a set of reusable components for tasks such as object detection, hand tracking, facial landmark detection, and gesture recognition.\nOne of the key features of MediaPipe is its ability to process real-time streaming data, making it well-suited for applications such as augmented reality and video surveillance. The framework also supports on-device and cloud-based deployment, allowing developers to choose the best option for their specific use case.\nThe MediaPipe ecosystem includes several pre-built solutions for common tasks, such as the Hand Tracking solution, which can detect and track hands in live video streams, and the Object Detection solution, which can detect and classify objects in images and videos. These solutions can be easily integrated into new or existing applications using the MediaPipe framework.\nMediaPipe on GitHub\nCode For this project, we will use Google Colab. Create a new notebook in Colab.\nYou can think of all the code snippets below are the cells of the colab notebook.\nInstall Dependencies 1%%bash 2pip install mediapipe Upload Image Upload any person's image to the Colab on which you want to perform the segmentation.\n1from google.colab import files 2uploaded = files.upload() This will let you upload files on the Colab environment. Let's say, I have uploaded the following image.\nPre-Processing and Display The Images 1import cv2 2from google.colab.patches import cv2_imshow 3import math 4import numpy as np 5 6DESIRED_HEIGHT = 480 7DESIRED_WIDTH = 480 8 9def resize_and_show(image): 10 h, w = image.shape[:2] 11 if h \u0026lt; w: 12 img = cv2.resize(image, (DESIRED_WIDTH, math.floor(h/(w/DESIRED_WIDTH)))) 13 else: 14 img = cv2.resize(image, (math.floor(w/(h/DESIRED_HEIGHT)), DESIRED_HEIGHT)) 15 cv2_imshow(img) 16 17 18# Read images with OpenCV. 19images = {name: cv2.imread(name) for name in uploaded.keys()} 20 21# Preview the images. 22for name, image in images.items(): 23 print(name) 24 resize_and_show(image) This code sets a desired height and width for images, defines a function called resize_and_show which takes an image as input, resizes the image based on whether the image's height is less than or greater than its width, and then displays the image using the cv2_imshow function from the google colab library.\nIt then reads in a set of images using OpenCV which has been already uploaded in Google Colab in the previous step, and calls the resize_and_show function on each image. Which displays each of the image.\nImport Required Module for Selfie Segmentation 1import mediapipe as mp 2 3mp_selfie_segmentation = mp.solutions.selfie_segmentation If you want to change the model selection parameter during the initialization. Run help(mp_selfie_segmentation.selfie_segmentation) to get more informations about the parameter. Like below:\n1help(mp_selfie_segmentation.SelfieSegmentation) MediaPipe Selfie Segmentation provides two models: general and landscape. Both models are based on MobileNetV3, with modifications to make them more efficient.\nThe general model operates on a 256x256x3 (HWC) tensor, and outputs a 256x256x1 tensor representing the segmentation mask. The landscape model is similar to the general model, but operates on a 144x256x3 (HWC) tensor. It has fewer FLOPs than the general model, and therefore, runs faster. Note that MediaPipe Selfie Segmentation automatically resizes the input image to the desired tensor dimension before feeding it into the ML models.\nDisplay Segmentation Masks 1BG_COLOR = (192, 192, 192) # gray color for the background 2MASK_COLOR = (255, 255, 255) # white color for the mask 3 4with mp_selfie_segmentation.SelfieSegmentation() as selfie_segmentation: 5 for name, image in images.items(): 6 # Convert the BGR image to RGB and process it with MediaPipe Selfie Segmentation. 7 results = selfie_segmentation.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 8 9 # Generate solid color images for showing the output selfie segmentation mask. 10 fg_image = np.zeros(image.shape, dtype=np.uint8) 11 fg_image[:] = MASK_COLOR 12 bg_image = np.zeros(image.shape, dtype=np.uint8) 13 bg_image[:] = BG_COLOR 14 condition = np.stack((results.segmentation_mask,) * 3, axis=-1) \u0026gt; 0.2 15 output_image = np.where(condition, fg_image, bg_image) 16 17 print(f\u0026#39;Segmentation mask of {name}:\u0026#39;) 18 resize_and_show(output_image) The above code is using OpenCV and MediaPipe library to segment images, it uses the MediaPipe Selfie Segmentation library to separate the background and the foreground of the image. The code then converts the image from BGR to RGB using the cv2.cvtColor() function to ensure that the image is in the correct format for processing. Then the image is processed with the MediaPipe Selfie Segmentation library, resulting in a mask of the foreground. The code then creates an output image by replacing the pixels in the mask with a specified color, and the pixels not in the mask with another specified color. Finally, the output image is displayed using the resize_and_show(output_image) function.\nOpenCV, by default, reads images in the BGR format, whereas most image processing libraries and libraries for displaying images expect images to be in the RGB format. The main difference between BGR and RGB color spaces is the order of channels. In BGR, the channels are ordered Blue, Green, and Red, while in RGB they are Red, Green, and Blue. This difference can lead to unexpected results when working with image processing libraries that expect images to be in the RGB format.\nThis code will display the mask like below: Blur the image background based on the segementation mask 1# opens the MediaPipe Selfie Segmentation library and assigns it to the variable \u0026#39;selfie_segmentation\u0026#39;. 2with mp_selfie_segmentation.SelfieSegmentation() as selfie_segmentation: 3 4 # iterates over the items in the \u0026#39;images\u0026#39; dictionary 5 for name, image in images.items(): 6 # Convert the BGR image to RGB and process it with MediaPipe Selfie Segmentation. 7 results = selfie_segmentation.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 8 9 # Apply a Gaussian blur to the image. 10 blurred_image = cv2.GaussianBlur(image,(55,55),0) 11 12 # compares the segmentation mask with a threshold of 0.5. 13 condition = np.stack((results.segmentation_mask,) * 3, axis=-1) \u0026gt; 0.5 14 15 # create an output image by replacing the pixels in the mask with the original image and 16 # the pixels not in the mask with the blurred image. 17 output_image = np.where(condition, image, blurred_image) 18 19 print(f\u0026#39;Blurred background of {name}:\u0026#39;) 20 21 # display the output image 22 resize_and_show(output_image) This code will output an image like below:\nLet's increase the intensity of the blurriness. Let's iterate the image 4 times through the GaussianBlur.\n1num_blur_iterations = 4 2 3with mp_selfie_segmentation.SelfieSegmentation() as selfie_segmentation: 4 for name, image in images.items(): 5 # Convert the BGR image to RGB and process it with MediaPipe Selfie Segmentation. 6 results = selfie_segmentation.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 7 8 blurred_image = image 9 for i in range(num_blur_iterations): 10 blurred_image = cv2.GaussianBlur(blurred_image,(55,55),0) 11 condition = np.stack((results.segmentation_mask,) * 3, axis=-1) \u0026gt; 0.1 12 output_image = np.where(condition, image, blurred_image) 13 14 print(f\u0026#39;Blurred background of {name}:\u0026#39;) 15 resize_and_show(output_image) Output Image would look like below:\nChange The Background to Black Now, let's change the background to black instead of making it blurred.\n1with mp_selfie_segmentation.SelfieSegmentation() as selfie_segmentation: 2 for name, image in images.items(): 3 # Convert the BGR image to RGB and process it with MediaPipe Selfie Segmentation. 4 results = selfie_segmentation.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 5 6 # create a black image with the same shape as the original image 7 black_image = np.zeros(image.shape, dtype=np.uint8) 8 condition = np.stack((results.segmentation_mask,) * 3, axis=-1) \u0026gt; 0.5 9 output_image = np.where(condition, image, black_image) 10 11 print(f\u0026#39;Black background of {name}:\u0026#39;) 12 resize_and_show(output_image) This will output an image like below:\nThe source code can be found here in this Github Repository.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/removing-background-selfie-segmentation/","section":"artificial-intelligence","tags":["project-tutorial","computer vision","machine learning"],"title":"How to Achieve Perfect Selfie Segmentation and Background Removal"},{"body":"","link":"https://blog.sksoumik.com/series/project/","section":"series","tags":null,"title":"project"},{"body":"","link":"https://blog.sksoumik.com/categories/project/","section":"categories","tags":null,"title":"project"},{"body":"","link":"https://blog.sksoumik.com/tags/project-tutorial/","section":"tags","tags":null,"title":"project-tutorial"},{"body":"","link":"https://blog.sksoumik.com/tags/bot-development/","section":"tags","tags":null,"title":"bot-development"},{"body":"Goal of The Project The project aims to build an Instagram bot that will auto-scroll pages down and Like/Love posts automatically. We must log in to Instagram from our web browser and then run the program.\nYou will find all the source code in this GitHub repository.\nDependencies 1pip install pyautogui 2# if you are on Linux, you also need to install scrot 3sudo apt-get install scrot Task 1 At first, we need to find the exact RGB value of the love sign on Instagram when the post has already been liked, so that our program doesn't click on the posts that has been already liked. When a post is already liked, it's usually marked as red. But the RGB value might change over time; we must check what its actual color value.\nThe following code finds the value of the Love Sign. Before running the following code:\nOpen instagram.com from your web browser, and sign in. Click on the heart icon of any post so that the heart sign becomes red. Run this code. Then point the mouse cursor to the heart sign. This will print the color value of the heart sign in the terminal Copy the color value and save it somewhere so that we can use it later in the main.py file. 1\u0026#34;\u0026#34;\u0026#34; 2This code finds the red color value of the heart sign of instagram. 3\u0026#34;\u0026#34;\u0026#34; 4 5import pyautogui as pt 6from time import sleep 7 8while True: 9 try: 10 positionXY = pt.position() 11 print(positionXY, pt.pixel(positionXY[0], positionXY[1])) 12 sleep(1) 13 14 if positionXY[0] == 0: 15 break 16 17 except Exception as e: 18 print(e) 19 pass Task 2 Now, we will split the computer window like below (browser on one side and code editor/terminal on another side).\nNow, run the main.py file which contains the following code.\n1import pyautogui as pt 2from time import sleep 3import random 4 5# red heart color value = (255, 0, 82); found from running the heart_color.py file. 6 7class GuiCommand: 8 def __init__(self, x, y): 9 self.x = x 10 self.y = y 11 12 13 def navigate_to_heart(self, speed): 14 # # Find the position of the \u0026#39;bookmark.png\u0026#39; image on the screen 15 position = pt.locateOnScreen(\u0026#39;bookmark.png\u0026#39;, confidence=.8) 16 17 # Calculate the x and y coordinates of the 18 # heart based on the position of the \u0026#39;bookmark.png\u0026#39; image 19 self.x = position[0] - 405 20 self.y = position[1] + 10 21 22 # Move the mouse cursor to the heart 23 pt.moveTo(self.x, self.y, duration=speed) 24 print(\u0026#39;Navigating to heart...\u0026#39;) 25 # Sleep for a random amount of time to add some variability 26 sleep(random.uniform(.3, .7)) 27 28 29 30if __name__ == \u0026#39;__main__\u0026#39;: 31 commands = GuiCommand(0, 0) 32 33 for i in range(0, 100): 34 try: 35 commands.navigate_to_heart(.1) 36 # Check if the current pixel color matches the color of the heart (255, 0, 88) 37 # If it does, it means that the heart is red, and it\u0026#39;s already been liked 38 # so we scroll down without clicking 39 if pt.pixelMatchesColor(pt.position().x, pt.position().y, (255, 0, 88), tolerance=10): 40 pt.scroll(-500) 41 sleep(random.uniform(.3, .9)) 42 43 else: 44 # If the heart is not red, then click on it 45 # and scroll down 46 pt.click() 47 print(\u0026#39;Heart clicked!\u0026#39;) 48 sleep(random.uniform(.2, 1.5)) 49 pt.scroll(-500) 50 51 except Exception as e: 52 print(e) 53 pt.scroll(-500) 54 sleep(random.uniform(.7, 2.0)) When you run this main.py file, your cursor will automatically move to the position of the heart sign of instagram and start giving auto-like to the posts. The bot will automatically scroll down pages and keep liking posts in the range that you define in the main.py file.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/building-automated-instagram-liker-bot/","section":"software-engineering","tags":["project-tutorial","bot-development","python"],"title":"Building an Instagram Auto-Liker Bot - A Step-by-Step Guide"},{"body":"","link":"https://blog.sksoumik.com/tags/python/","section":"tags","tags":null,"title":"python"},{"body":"Can you explain the bias-variance trade-off and how it relates to model performance? Machine learning and statistics have a fundamental concept that requires balancing the model's bias and variance, known as the bias-variance trade-off. These two types of errors can affect a model's performance.\nBias, a type of error, occurs when a model makes assumptions about the data that are too simplistic. High bias means the model is too simple to capture the underlying patterns. This usually leads to underfitting.\nVariance, on the other hand, is another type of error that occurs when your model is too sensitive. It memorizes the training data, but fails when given new data.\nThe trade-off:\nIf you increase model complexity, you reduce bias but increase variance. If you simplify the model, you reduce variance but increase bias. The goal is to find a balance so the model learns real patterns, not the noise.\nExplain the concept of model overfitting and underfitting Overfitting and underfitting refer to a model that works well on the training data but not on new data that the model has not seen earlier.\nOverfitting occurs when a model is too complex and sensitive to the specific details of the training data. This makes the model work well on the training data but badly on new data as it learns patterns only specific to the training data and not applicable to other data.\nOn the other hand, underfitting occurs when the model is too simple to capture the patterns in the data. This makes the model perform badly on both training and new data.\nSo,\n1Underfitting -\u0026gt; High Bias, Low Variance -\u0026gt; Model too simple, misses patterns 2Overfitting -\u0026gt; Low Bias, High Variance -\u0026gt; Model too complex, memorizes noise Both overfitting and underfitting can cause poor performance for the task that the model was trained for. To avoid them, data scientists must create a balance between the model's complexity and the amount of training data. This often involves regularization to control the model's complexity and cross-validation to evaluate the model's performance on new data.\nWhat are the differences among unsupervised, semi-supervised, and supervised learning? Unsupervised learning algorithms don't require any explicit direction since the model learns from unlabeled data and must independently uncover the patterns and structures within it. Some examples of unsupervised learning include anomaly detection, clustering, and dimensionality reduction. Imagine you have a large dataset of customer purchases from an online store, but the data doesn't have any labels or categories. With unsupervised learning, you can use clustering algorithms to group similar purchases together based on their features (like price, category, or time of purchase) and discover underlying patterns or trends in the data.\nSemi-supervised learning is utilized when some labeled outputs exist in the training dataset, but not all. By using the labeled data, the model learns the relationship between the inputs and outputs, which it can apply to predict the unlabeled data. This method is particularly useful when there isn't enough labeled data to train a supervised model, but enough unlabeled data exists to support the model. Suppose you are building a spam detection system for emails. You have a small labeled dataset of spam and non-spam emails, but you also have a large unlabeled dataset. With semi-supervised learning, you can use the labeled data to train a model to identify patterns in the data and predict which emails are likely to be spam. The model can then use this knowledge to classify the unlabeled data and identify new spam emails.\nSupervised learning trains the model on labeled input-output pairs, where it learns to map the inputs to the outputs. Once trained, the model can make predictions on unseen data. Common examples of supervised learning include classification, regression, and structured prediction. Let's say you want to build a model to predict whether a customer will purchase a product based on their demographic information. You have a labeled dataset of customer demographics and purchase history. You can use this data to train a supervised learning model, such as logistic regression or decision trees, to learn the relationship between the customer demographics and the purchase outcome. Once trained, the model can predict whether a new customer is likely to purchase the product based on their demographic information.\nCan you explain the concept of overfitting and how to avoid it? Sometimes machine learning models perform well on training data but poorly on new or unseen data. This is called overfitting. It happens when the model has learned the noise or random fluctuations in the training data, instead of the underlying patterns and trends. This makes the model incapable of generalizing well to new data and hence, its predictions are inaccurate.\nThere are several approaches to avoid overfitting. First, we can provide a larger training dataset to help the model learn better from the data, and this makes the patterns stronger and more useful in many situations. Second, regularization is a technique that involves adding a penalty term to the cost function. This encourages the model to use simpler and more generalizable models. Third, cross-validation is another technique that involves dividing the training dataset into multiple sets, training the model on one set, and evaluating it on the other sets. This technique can help identify overfitting and provide a more accurate evaluation of the model.\nFourth, early stopping is a technique that monitors the performance of the model on a validation set during training and stops the training process when the performance on the validation set begins to decrease. This can help prevent the model from learning the noise in the training data. Finally, ensembling is a technique that involves training multiple models on the same data and combining their predictions. This technique can help reduce overfitting by averaging out the noise in the individual models.\nHow regularization helps to prevent overfitting Regularization is a technique that is used to prevent overfitting. It does this by adding a penalty to the model's loss function. This penalty helps to constrain the model and prevent it from learning the noise in the training data.\nThere are different types of regularization techniques, such as L1 regularization, L2 regularization, and dropout.\nL1 and L2 regularization are techniques that are used to constrain a model's weights. They are both forms of regularization that add a penalty to the model's loss function. The goal of regularization is to prevent overfitting by encouraging the model to use simpler, less complex solutions.\nL1 regularization, also known as Lasso regularization, adds a penalty that is proportional to the absolute value of the weights. It is defined as follows:\n1L1_regularization = lambda_reg * np.sum(np.abs(weights)) where:\nlambda_reg is the regularization strength hyperparameter. weights are the model parameters or weights. np.abs() computes the absolute value of each weight. np.sum() sums up the absolute values of all weights. L1 regularization will shrink the weights of the model towards zero, with the goal of eliminating the least important features.\nL2 regularization, also known as Ridge regularization, adds a penalty that is proportional to the square of the weights. It is defined as follows:\n1L2_regularization = lambda_reg * np.sum(np.square(weights)) L2 regularization will shrink the weights of the model towards zero, but it will not eliminate any features. Instead, it will distribute the weight among all of the features, with the goal of reducing the complexity of the model.\nDropout is a regularization technique that is used to prevent overfitting in neural networks. It works by randomly setting a fraction of the model's units to zero during training. This forces model to learn multiple, independent representation of the same data, which helps to prevent the model from relying too much on any one unit.\nIn Keras, you can use L1 and L2 regularization by including the L1 or L2 argument in the kernel_regularizer or activity_regularizer argument when defining the layers of your model.\nL1 regularization in a Keras model:\n1from tensorflow.keras import regularizers 2 3model = Sequential() 4model.add(Dense(64, input_shape=(64,), kernel_regularizer=regularizers.L1(0.01))) 5model.add(Dense(32, kernel_regularizer=regularizers.L1(0.01))) 6model.add(Dense(10, activation=\u0026#39;softmax\u0026#39;)) In this example, the L1 regularization strength is set to 0.01. You can adjust this value to control the strength of the regularization.\nTo use L2 regularization in a Keras model, you can use the L2 argument in the kernel_regularizer or activity_regularizer or bias_regularizer argument. Here is an example:\n1from tensorflow.keras import regularizers 2 3model = Sequential() 4model.add(Dense(64, input_shape=(64,), kernel_regularizer=regularizers.L2(0.01))) 5model.add(Dense(32, kernel_regularizer=regularizers.L2(0.01))) 6model.add(Dense(10, activation=\u0026#39;softmax\u0026#39;)) As with L1 regularization, you can adjust the value of the l2 argument to control the strength of the regularization.\nYou can also use both L1 and L2 regularization in the same model by including both L1 and L2 arguments in the kernel_regularizer or activity_regularizer argument.\n1from tensorflow.keras import regularizers 2 3model = Sequential() 4model.add(Dense(64, input_shape=(64,), kernel_regularizer=regularizers.L1L2(l1=1e-5, l2=1e-4))) 5 6model.add(Dense(32, kernel_regularizer=regularizers.L1L2(l1=1e-5, l2=1e-4), 7 bias_regularizer=regularizers.L2(1e-4), 8 activity_regularizer=regularizers.L2(1e-5)) 9 10model.add(Dense(10, activation=\u0026#39;softmax\u0026#39;)) See Keras doc\nHow would you explain what a random forest regression model is to a non-technical stakeholder? Random forest regression model is a type of predictive model/algorithm that can help us to estimate the numerical value of something based on its characteristics. Consider that you want to purchase a used car and are curious about its expected price. You may consider things like model of the car, its mileage, brand, age, and condition.\nA random forest regression model takes all of these factors into account and uses them to make a prediction about the car's price.\nThe name \u0026quot;random forest\u0026quot; refers to the fact that it's made up of many different decision trees, each of which looks at the data from a slightly different angle. Each decision tree makes a prediction about the car's price based on a subset of the available data. The model then combines all of these individual predictions to arrive at a final estimate of the car's price.\nFor example, let's say we have a dataset of used car sales that has information about each car's model, mileage, age, condition, and price. We can use a random forest regression model to analyze this data and identify the factors that have the biggest impact on the car's price. Then we can train a Random forest regression model to learn about this relationship.\nOnce the model has been trained on this data, we could use it to estimate the price of a specific used car by inputting its characteristics into the model. The model would then use its knowledge of the relationship between these characteristics and the car's price to make a prediction about what the car should cost.\nImportant Parameters of a Random Forest Model Number of trees (n_estimator): The number of trees parameter determines how many individual decision trees the random forest model will use to make its prediction. A larger number of trees can result in a more accurate model, but can also require more computational power and take longer to train.\nMax depth (max_depth): The max depth parameter sets a limit on the maximum depth of each individual decision tree in the random forest model. This parameter is important because it can prevent overfitting, which is when the model is too closely tailored to the training data and doesn't generalize well to new data. A smaller max depth can help prevent overfitting, but can also result in a less accurate model.\nMinimum samples split (min_samples_split): The minimum samples split parameter sets the minimum number of samples required to split an individual node in a decision tree. This parameter is important because it can help prevent overfitting by ensuring that each node has enough samples to make an accurate split. A smaller minimum samples split can result in overfitting, while a larger value can result in underfitting.\nMinimum samples leaf (min_samples_leaf): The minimum samples leaf parameter sets the minimum number of samples required to be in a leaf node of the decision tree. This parameter is important because it can help prevent overfitting by ensuring that each leaf node has enough samples to make an accurate prediction. A smaller minimum samples leaf can result in overfitting, while a larger value can result in underfitting.\nMax features (max_features): The max features parameter sets the maximum number of features that are considered when determining the best split for each node in a decision tree. This parameter is important because it can help prevent overfitting by limiting the number of features that the model considers. A smaller max features value can help prevent overfitting, but can also result in a less accurate model.\nWhat is the difference between parametric and non-parametric model In Machine Learning, we use different algorithms to learn from data and make predictions or decisions. We can use different types of models to do this, but two important types are called \u0026quot;parametric\u0026quot; and \u0026quot;non-parametric.\u0026quot;\nA parametric model is like a recipe where we know all the ingredients and how much of each to use. Once we have these ingredients and amounts, we can use the recipe to make something. In Machine Learning, this means that we assume the data follows a certain mathematical formula or data distribution, and we try to find the best values for the parameters of that formula based on the data. For example, a linear regression model is a parametric model where we assume the data follows a straight line.\nA non-parametric model is more like a mystery where we don't know what the ingredients are or how much of each to use. Instead, we try to learn from the data itself and use what we learn to make predictions or decisions. In Machine Learning, this means that we don't make any assumptions about the mathematical formula or data distribution that describes the data. Instead, we try to find patterns or relationships in the data itself that we can use to make predictions. For example, a decision tree model is a non-parametric model where we don't assume any particular formula for the data, but instead make decisions based on the features of the data.\nExamples:\nParametric models:\nLinear Regression: we assume that the data follows a normal distribution, also known as a Gaussian distribution. This means that the target variable (i.e., the variable we want to predict) is normally distributed around the mean value of the target variable, given a specific set of input features.\nMore formally, we assume that the target variable y can be expressed as a linear combination of the input features.\nLogistic Regression: Bernoulli distribution, which is a type of binary distribution. The Bernoulli distribution models the probability of success or failure for a single binary outcome (e.g., heads or tails, yes or no, 1 or 0), and is characterized by a single parameter, which is the probability of success.\nSimple feedforward neural network\nNon-parametric models:\nK-Nearest Neighbors (KNN) Decision Tree SVM CNN RNN How back propagation algorithm works Imagine you have a friendly robot who wants to get better at a task, like catching a ball. You give him advice when it make mistakes and show them how to improve. The robot adjusts its moves based on your tips and tries again. This is similar to how the backpropagation algorithm works.\nIn ML, we have artificial neural networks that aim to learn and improve at things like recognizing pictures or understanding language. The backpropagation algorithm acts like a teacher for these networks.\nHere's the gist: The neural network is like the robot, made up of interconnected parts called neurons. When the network messes up, we compare its answer to the correct one and figure out how each neuron contributed to the mistake.\nThen, we go backward through the network, starting from the end and going back to the beginning. At each neuron, we tweak its strength or \u0026quot;weight\u0026quot; depending on its role in the mistake. It's like guiding the robot to improve specific parts of its movement.\nWe repeat this process of comparing, calculating, and adjusting the weights many times until the neural network gets better at the task. Just as the robot practices catching the ball repeatedly to become really good at it, the neural network improves its abilities with the help of the backpropagation algorithm.\nHow dropout works Dropout is a method is used to prevent neural networks from overfitting. During training, a portion of the input units are randomly set to zero. This means that these units are \u0026quot;dropped out,\u0026quot; or ignored, when making predictions.\nHere's how it works:\nAt each training step, a proportion of the input units is set to zero. This proportion is called the dropout rate and is typically set between 0.2 and 0.5. The remaining units are scaled down by a factor of 1/(1-dropout_rate). This is done so that the mean output of the units is preserved. The network is trained as usual, with the dropped-out units ignored. At test time, all of the units are used and the output of the network is scaled up by the same factor used to scale down the units during training. The idea behind dropout is that, by dropping out a random subset of the units in the network, the model is forced to rely on a more diverse set of features, rather than overfitting to a specific set of features. This makes the model more robust and less prone to overfitting.\nWhat is Transfer Learning? Transfer learning is a machine learning technique where models are trained on one task is used as the starting point for a model on a second, related task. This can help to improve the performance of the second model by levaraging the knowledge learned from the first task.\nFor example, if a model is trained to recognize objects in photographs, it will learn to recognize features such as edges, textures, and shapes that are commonly found in images. This knowledge can be useful for other tasks that involve image recognition, such as classifying medical images or detecting objects in videos. By using the model trained on the first task as a starting point, the second model can learn more quickly and achieve better performance.\nTransfer learning is often used in deep learning, where it can help to overcome the challenges of training large and complex models on small datasets. It is also a useful technique for adapting a model to a new domain or to improve its performance on a specific task.\nDifference between parameter and hyper-parameter in Machine Learning In machine learning, a parameter is a value that is learned by a model during training. For example, in a linear regression model, the parameters are the coefficiants that are used to make predictions. These parameters are learned by the model based on the training data, and they are used to make predictions on new, unseen data.\nOn the other hand, hyperparameter is a value that is set by the data scientist before training. Hyperparameters are not learned by the model during training, but they can impact the performance and behavior of the model. Examples of hyperparameters include the learning rate used by a neural network, the regularization parameter in a regularized regression model, or the number of trees in a random forest.\nIn general, parameters are learned by the model, while hyperparameters are set by the data scientist. Tuning the hyperparameters of a model can often improve its performance, but this requires a good understanding of the model and the data it is being applied to.\nWhat will you do if your training data classification accuracy is 80% and test data accuracy is 60%? If the training data classification accuracy is 80% and the test data accuracy is 60%, it is likely that the model is overfitting to the training data. This means that the model has learned patterns that are specific to the training data, but that do not generalize well to new, unseen data.\nTo improve the performance of the model on the test data, there are several steps that you can take:\nUse more and/or different training data: This can help the model to learn more generalizable patterns, and may improve its performance on the test data. Simplify the model: By reducing the complexity of the model, you can reduce the risk of overfitting and improve its performance on the test data. This can be done by using regularization or by redusing the number of parameters in the model. Use techniques to prevent overfitting: There are many techniques that can be used to prevent overfitting, such as early stopping, dropout, or data augmentation. These techniques can help the model to generalize better to new data. Overall, the goal is to find a balance between model complexity and the amount of training data available, in order to achieve good performance on the test data. This often involves experimentation and trial and error to find the best combination of techniques and hyperparameters.\nTest accuracy is higher than the train accuracy. What does it indicate? If the test accuracy is higher than the training accuracy, it may indicate that the model is underfitting the training data. This means that the model is not able to capture the underlying patterns in the training data, and as a result, it does not perform well on the training data.\nHowever, it is also possible that the test accuracy is higher than the training accuracy due to random fluctuations in the data, or because the test data is easier to classify than the training data. In this case, the model may still be overfitting to the training data, and its performance on new, unseen data may be poor.\nIn general, it is important to evaluate the performance of a model on both the training data and the test data, and to compare the two in order to assess the model's ability to generalize to new data. If the test accuracy is significantly higher than the training accuracy, it may be necessary to adjust the model or to use different training data in order to improve its performance.\nIf layer normalization is removed from the GPT architecture, the performance of the model maybe negatively affected, why? Layer normalization is a technique used in neural networks to reduce the internal covariate shift that occurs during training. It is used to normalize the activations of each layer of the transformer model in the context of the GPT architecture. Removing layer normalization from the GPT architecture may negatively affect the performance of the model in several ways.\nFirstly, layer normalization helps to improve the stability of the training process by reducing the variance in the input distribution to each layer. Without this normalization, the input distribution to each layer would be more varied, leading to difficulties in training and slower convergence.\nSecondly, layer normalization helps to improve the generalization performance of the model by reducing the impact of feature correlations. Without this normalization, the model may rely too much on specific features and fail to generalize well to new data.\nCovariate shift refers to the situation in which the input distribution of a model changes over time or between different parts of the dataset. This can occur when the statistical properties of the input data change, such as when the mean or variance of the input features changes. Covariate shift can be problematic for machine learning models because it can lead to poor generalization performance.\nLet's say during a machine learning model training, you faced memory error. How would you solve it? Reduce batch size: The batch size determines how many samples are processed in one iteration during training. If the batch size is too large, it can cause a memory error. Therefore, reducing the batch size can help to reduce memory usage. However, reducing the batch size may also increase the training time.\nReduce model size: If the model is too large, it can cause a memory error. Therefore, reducing the size of the model can help to reduce memory usage. This can be done by reducing the number of layers, reducing the number of hidden units per layer, or reducing the size of the input data.\nUse a generator: If the data set is too large to fit into memory, a generator can be used to load the data in batches during training. This can help to reduce memory usage by only loading a small batch of data into memory at a time.\nUse mixed precision training: Mixed precision training is a technique that uses lower-precision data types (e.g., float16) for certain parts of the training process. This can help to reduce memory usage and speed up training.\nUse distributed training: Distributed training is a technique that uses multiple GPUs or machines to train the model. This can help to reduce memory usage by distributing the workload across multiple devices.\nUpgrade hardware: If none of the above solutions work, upgrading the hardware (e.g, using a GPU with more memory) may be necessary to solve the memory error.\nExplain the difference between a parametric and a non-parametric model. Give an example of each. A parametric model is a model that has a fixed number of parameters that are learned from the training data. Once the parameters have been learned, the model is fixed and can be used to predict on new data. A non-parametric model, on the other hand, does not have a fixed number of parameters, and the number of parameters may depend on the training data size.\nA simple example of a parametric model is linear regression. In linear regression, the model assumes that the relationship between the input and output variables is linear and can be described by a fixed set of parameters (e.g., the coefficients in a linear equation). Once these parameters have been learned from the training data, the model can be used to predict on the new data by applying the same set of parameters.\nAn example of a non-parametric model is k-nearest neighbors (KNN). In KNN, the model does not make any assumptions about the functional form of the relationship between the input and output variables. Instead, the model stores the entire training dataset and makes predictions on new data by finding the k nearest neighbors in the training dataset and using their output values to predict the output value for the new data point.\nThe main advantage of parametric models is that they are computationally efficient and can be trained quickly on large datasets. However, they may not be able to capture complex relationships between the input and output variables. Non-parametric models, on the other hand, are more flexible and can capture complex relationships between the input and output variables. However, they can be computationally expensive and may require a large amount of memory to store the training dataset.\nWhat are the main differences between GPT and GAN? GPT and GAN are both types of generative models used in machine learning. GPT generates new text based on a large corpus of text data it is trained on, while GAN generates new data like images, sounds, or text by using two neural networks called a generator and a discriminator. GPT uses a transformer architecture to predict the next word in a sequence of words and generate new text by sampling from the probability distribution of the next word, while GAN uses an adversarial training process to produce increasingly realistic samples.\nIn summary, the main differences between GPT and GAN are:\nGPT generates new text, while GAN generates new data like images, sounds, or text. GPT uses a transformer architecture, while GAN uses a generator and discriminator network. GPT generates new text by sampling from the probability distribution of the next word, while GAN generates new data by training a generator network to produce samples that are indistinguishable from real data. What does it mean by \u0026quot;adversarial training process\u0026quot; in GAN? Adversarial training is a technique used in machine learning to make models more resistant to adversarial attacks. Adversarial attacks are when an adversary tries to trick the model by making small changes to the input data that are hard for humans to notice.\nTo make models more resistant to these attacks, adversarial training involves generating adversarial examples during the training process and using them to update the model parameters. For example, in a generative adversarial network (GAN), the generator network is trained to produce samples that are indistinguishable from real data, while the discriminator network is trained to correctly classify whether a given sample is real or generated.\nThis technique can be used to train other types of models, such as image classifiers or natural language processing models, to be more resistant as well. By including adversarial examples in the training process, the model learns to identify and correct for these types of changes, which leads to better generalization and robustness in real-world applications.\nDoes a machine learning model with more parameters necessarily mean it is more powerful? The number of parameters in a machine learning model does not necessarily determine its power or effectiveness. Instead, a model's ability to accurately generalize to new data is the determining factor. A model with fewer parameters may actually perform better than a model with more parameters if it is better at generalizing to new data. This is because a more complex model with more parameters can lead to overfitting, where the model becomes too focused on the training data and is unable to generalize well to new data.\nHowever, if a model with more parameters is properly regularized and trained on a sufficiently large and diverse dataset, it can potentially achieve better performance than a simpler model. Therefore, other factors, such as regularization techniques, dataset size and quality, and model architecture, should also be considered when assessing the performance of a machine learning model.\nExplain data leakage Data leakage occurs in machine learning when information from outside the training data is used to create the model, resulting in a model that is overly optimistic and not representative of the true relationship between the features and the target variable. This can happen in a number of ways, such as using information from the test set to inform model training, or using data that is not actually available at the time the model will be used in practice. Data leakage can significantly bias model performance, leading to overly optimistic results on the training data and poor performance on new, unseen data. To prevent data leakage, it is important to carefully split the data into training and test sets, and to use only the training data to train the model.\nHow to improve the performance of a machine learning model? One of the first steps in improving the performance of a machine learning model is to identify the specific problem or issue with the model's performance. This might involve analyzing the model's performance on different subsets of the data, or comparing its performance to other models. Once you have identified the problem, you can take steps to address it.\nFor example, if the model is overfitting to the training data, you can try using regularization techniques to constrain the model and prevent overfitting. Regularization involves adding additional constraints to the model, such as limiting the number of features or the complexity of the model, to prevent the model from fitting too closely to the training data. This can help the model learn more generalizable patterns in the data, and improve its performance on new, unseen data.\nIf the model is underfitting, on the other hand, you can try increasing the complexity of the model by adding more features or using a more complex model architecture. By adding more features, the model can learn more intricate patterns in the data, which can improve its performance. Similarly, using a more complex model architecture, such as a deep neural network, can allow the model to capture more complex patterns in the data and improve its performance.\nAnother important step in improving the performance of a machine learning model is to carefully tune the model's hyperparameters. Hyperparameters are the parameters of the model that are not learned during training, such as the learning rate or regularization strength. By carefully tuning these hyperparameters, you can help the model learn more effectively and improve its performance. This can involve using techniques such as grid search or random search to explore different combinations of hyperparameters and identify the ones that yield the best performance.\nIn addition to these steps, it is also important to use different evaluation metrics to assess the model's performance. Instead of using accuracy alone, you can consider using other metrics such as precision, recall, or F1 score to get a more complete picture of the model's performance. These metrics can provide a more nuanced view of the model's performance, and can help you identify areas where the model is performing well or poorly.\nFinally, it is often helpful to try different approaches and techniques to improve the performance of a machine learning model. For example, you can try using ensemble methods, where multiple models are combined to make predictions, or transfer learning, where a pre-trained model is fine-tuned for a specific task. These approaches can help improve the model's performance by leveraging the strengths of multiple models or pre-existing knowledge.\nOverall, improving the performance of a machine learning model involves a combination of identifying and addressing specific problems with the model, tuning the model's hyperparameters, using different evaluation metrics, and experimenting with different approaches and techniques. By following these steps, you can help your model learn more effectively and make more accurate predictions on new, unseen data.\nWhat are the different types of regression models? Linear Regression Linear regression is used to make predictions about a continuous outcome based on one or more factors. It assumes that the relationship between the factors and the outcome is straight and proportional.\nAlthough linear regression is helpful, it has limitations that can make it less accurate in certain situations. Here are some common cases where linear regression may not work well:\nWhen the relationship between the factors and the outcome is not straight. Linear regression assumes a straight relationship, so it may not perform well if the relationship is curved or nonlinear. When the data is noisy or has extreme values. Linear regression can be affected by noisy data or outliers, which can lead to inaccurate predictions. When there are interactions or complexities in the data. Linear regression cannot handle interactions or nonlinear patterns in the data, so it may not be suitable in these cases. When the factors are highly related. Linear regression assumes independence between factors, so if the factors are highly correlated, it may not provide reliable results. Polynomial Regression In regression analysis, polynomial regression stands as a different approach. Rather than assuming a straight line, it allows for a more intricate, curvy relationship between the variables.\nPolynomial regression proves helpful when we encounter situations where the connection between the variables is not a simple line. For example, if we are trying to predict stock prices based on historical performance, the relationship between price and time can be quite complicated. By implementing polynomial regression, we can capture this complexity and enhance our predictive capabilities.\nHowever, it is essential to consider that polynomial regression is not always the best option. If the connection between the variables is actually linear, using a simpler linear regression model would be more appropriate. Linear regression offers ease of interpretation and computation, making it a better fit in such cases, especially when working with large datasets.\nLasso Regression Lasso regression, also known as L1 regularization, is a type of regression that uses a regularization term in the cost function to penalize the complexity of the model. This regularization term, known as the L1 norm, adds a penalty based on the absolute value of the coefficients of the model, with the goal of reducing the magnitude of the coefficients and limiting the model's complexity.\nLasso regression is useful in situations where the number of predictor variables is very large, and some of the predictor variables are not actually relevant for predicting the outcome variable. By using the L1 regularization term, Lasso regression can automatically select the most important predictor variables and ignore the others, reducing the model's complexity and improving its performance.\nOn the other hand, Lasso regression may not be the best choice in situations where the number of predictor variables is small, or where all of the predictor variables are equally important. In these cases, Lasso regression may select only a few predictor variables and ignore the rest, potentially leading to poorer performance. Additionally, Lasso regression may perform poorly when the predictor variables are highly correlated, as it can only select one of the correlated variables.\nRidge Regression Ridge regression, also known as L2 regularization, is a type of regression that uses a regularization term in the cost function to penalize the complexity of the model. This regularization term, known as the L2 norm, adds a penalty based on the squared value of the coefficients of the model, with the goal of reducing the magnitude of the coefficients and limiting the model's complexity.\nRidge regression is useful in situations where the number of predictor variables is very large, and some of the predictor variables are not actually relevant for predicting the outcome variable. By using the L2 regularization term, Ridge regression can automatically reduce the magnitude of the coefficients of the less important predictor variables, reducing the model's complexity and improving its performance.\nOn the other hand, Ridge regression may not be the best choice in situations where the number of predictor variables is small, or where all of the predictor variables are equally important. In these cases, Ridge regression may still reduce the magnitude of the coefficients of the less important predictor variables, potentially leading to poorer performance. Additionally, Ridge regression may perform poorly when the predictor variables are highly correlated, as it will reduce the magnitude of all of the correlated variables, rather than selecting only one of them.\nOverall, Ridge regression is a useful tool for reducing the complexity of a regression model and automatically reducing the magnitude of the coefficients.\nDifference between Lasso and Ridge Regression Imagine you have a basket filled with different types of balls, like basketballs, soccer balls, and tennis balls. Now, let's say you want to find the best way to predict the weight of each ball based on their size, color, and texture. This is where regression comes in.\nRegression is like a special tool that helps us make predictions by finding patterns in the data. Lasso and Ridge regression are two different techniques we can use when we have a lot of features (or characteristics) to consider.\nLasso regression is like a strict coach who wants to make the prediction as accurate as possible but with fewer features. It helps us select only the most important features by shrinking the less important ones to zero. It's like removing the soccer balls and tennis balls from our basket, and only keeping the basketballs because they have the most impact on the weight prediction.\nOn the other hand, Ridge regression is like a more flexible coach who allows some of the less important features to contribute a little bit to the prediction. It doesn't completely eliminate them like Lasso regression. It's like keeping all the balls in the basket but reducing their impact based on their importance.\nSo, the main difference between Lasso and Ridge regression is how they handle the features. Lasso picks the most important ones and removes the less important ones, while Ridge keeps all the features but reduces their influence if they are less important.\nWhich one is best?\nThe choice between Lasso and Ridge regression depends on the specific problem and the characteristics of the data. There is no definitive answer as to which one is better overall, as it varies based on the context and goals of the analysis.\nLasso regression is often preferred when there is a belief or evidence that only a subset of the features are truly important for making accurate predictions. It has the ability to automatically select and eliminate less important features by setting their corresponding weights to zero. This can help simplify the model and improve interpretability. Therefore, if you have a high-dimensional dataset with many features but suspect that only a few are truly influential, Lasso regression may be a good choice.\nOn the other hand, Ridge regression is suitable when all the features are potentially relevant and you want to include them in the model. It reduces the impact of less important features by shrinking their weights towards zero, rather than completely eliminating them. This can be beneficial in situations where all the features may collectively contribute to the prediction, even if some have weaker effects. Ridge regression can help mitigate multicollinearity (high correlation between features) and improve the model's stability.\nUltimately, the better choice between Lasso and Ridge regression depends on the specific characteristics of your dataset, the goals of your analysis, and your understanding of the underlying relationship between the features and the target variable. It is recommended to try both approaches and assess their performance using appropriate evaluation metrics or techniques such as cross-validation to determine which method works best for your particular problem.\nBayesian Linear Regression Bayesian linear regression is a type of linear regression that uses Bayesian statistics to make inferences about the model parameters. In Bayesian linear regression, the model parameters are treated as random variables, and a probability distribution is used to represent our uncertainty about their values. This allows the model to incorporate prior knowledge and make more accurate predictions based on the data.\nBayesian linear regression is useful in situations where you have prior knowledge about the model parameters, or where you want to incorporate uncertainty in the model predictions. For example, if you have previously collected data on the relationship between the dependent and independent variables, you can use this data to inform the prior distribution of the model parameters in a Bayesian linear regression model. This can improve the model's performance and make more accurate predictions.\nOn the other hand, Bayesian linear regression may not be the best choice in situations where you do not have prior knowledge about the model parameters, or where you do not need to incorporate uncertainty in the model predictions. In these cases, a standard linear regression model may be more appropriate, as it is simpler and faster to train. Additionally, Bayesian linear regression can be computationally expensive, so it may not be practical for very large datasets.\nWays to identify outliers in a dataset There are several ways to identify outliers in a dataset:\nVisualization: One of the most effective ways to identify outliers is to visualize the data using a scatter plot or box plot. Outliers will typically be plotted as individual points that are far from the majority of the data.\nStatistical tests: There are various statistical tests that can be used to identify outliers, such as the Z-score test or the Tukey method. These tests identify points that are significantly different from the rest of the data.\nData cleaning: Another way to identify outliers is to check the data for errors or inconsistencies. For example, if the data includes a column of ages and there is an entry for an age of 200 years old, this could be an outlier due to an error in data entry.\nHow to perform Z-score test in Python The Z-score test, also known as the Standard Score test, can be used to identify outliers in a dataset by calculating the number of standard deviations each data point is from the mean. Data points that are more than a certain number of standard deviations from the mean can be considered outliers.\nHere's an example of how to perform the Z-score test in Python:\n1import numpy as np 2 3# Calculate the mean and standard deviation of the data 4mean = np.mean(data) 5std = np.std(data) 6 7# Identify the outliers using the Z-score test 8outliers = [] 9for datapoint in data: 10 z_score = (datapoint - mean) / std 11 if np.abs(z_score) \u0026gt; threshold: 12 outliers.append(datapoint) In this example, data is a list or array of data points, and threshold is the number of standard deviations that a data point must be from the mean to be considered an outlier. The mean and standard deviation of the data are calculated using the mean and std functions from NumPy, and the Z-scores of each data point are calculated using the formula (datapoint - mean) / std. The outlier threshold can be set based on the desired level of sensitivity. For example, a threshold of 3 standard deviations is often used, which corresponds to a confidence interval of 99.7%.\nWhat does standard deviation tell you? A standard deviation (or σ) is a measure of how dispersed the data is in relation to the mean. Low standard deviation means data are clustered around the mean, and high standard deviation indicates data are more spread out.\nHow to calculate std: It is calculated as the square root of the variance. Variance is the average of the squared differences between the data points and the mean.\npopulation standard deviation the size of the population each value from the population the population mean Ref: Wikipedia\nHow do we understand from the data that we need to apply linear regression? There are a few ways you can understand from the data that linear regression might be an appropriate model to use:\nLinear relationship: If you plot the independent and dependent variables and observe a linear pattern, it suggests that a linear model might be appropriate. You can use a scatterplot to visualize this relationship. Correlation: If the independent and dependent variables are correlated, it suggests that a linear model might be appropriate. You can use a correlation coefficient (such as Pearson's r) to measure the strength and direction of the correlation. Data type: If the dependent variable is continuous and the independent variables are either continuous or categorical, linear regression might be appropriate. If the dependent variable is categorical, you might want to consider using logistic regression instead. Problem type: If you are trying to predict a continuous variable based on other variables, linear regression might be appropriate. If you are trying to classify data into different categories, you might want to consider using a different model such as logistic regression or a decision tree. How to evaluate a linear regression model? There are a number of ways to evaluate a linear regression model to assess its performance and understand its strengths and limitations. Here are a few common evaluation metrics:\nR-squared (R^2): This is a measure of how well the model fits the data. It ranges from 0 to 1, with a higher value indicating a better fit. R^2 is calculated as 1 - (SSR/SST), where SSR is the sum of squared residuals (the difference between the predicted and actual values) and SST is the total sum of squares (the difference between the actual values and the mean of the dependent variable). Mean squared error (MSE): This is a measure of the average squared difference between the predicted and actual values. A lower MSE indicates a better fit. Mean absolute error (MAE): This is a measure of the average absolute difference between the predicted and actual values. A lower MAE indicates a better fit. Root mean squared error (RMSE): This is the square root of the MSE and is interpreted in the same units as the dependent variable. A lower RMSE indicates a better fit. F-statistic: This is a measure of the overall significance of the model. A high F-statistic indicates that the model is significantly better than a model with no predictors (i.e., a horizontal line). Why Logistic Regression algorithm named as regression even though it's used for classification The name \u0026quot;logistic regression\u0026quot; is used because the model is an extension of linear regression, which is used to predict a continuous outcome. However, logistic regression is used for classification, not regression. The model is called \u0026quot;logistic\u0026quot; because it uses the logistic function as the activation function for the model. The logistic function is used to predict the probability that an example belongs to a certain class. The output of the logistic function is always between 0 and 1, which can be interpreted as the probability that the example belongs to the positive class.\nThe logistic function, also known as the sigmoid function, is a mathematical function that maps any input to a value between 0 and 1. It is defined as follows:\n$$ f(x) = \\frac{1}{1 + e^{-x}} $$\nwhere e is the base of the natural logarithm, approximately 2.718.\nThe logistic function has a \u0026quot;S\u0026quot; shape. The output of the function is always between 0 and 1, which makes it convenient for predicting probabilities.\nThe logistic function is often used as the activation function in neural networks and in logistic regression. In logistic regression, the output of the logistic function is interpreted as the probability that an example belongs to the positive class. The class that the example is assigned to is determined by thresholding the output of the logistic function. For example, if the output is greater than 0.5, the example is classified as the positive class, and if the output is less than 0.5, the example is classified as the negative class.\nWhy do we normalize data in Machine Learning? Normalizing data in machine learning is the process of scaling the data so that it has a mean of zero and a standard deviation of one. This is typically done to improve the performance of the machine learning model, by ensuring that the data is in a standardized range and allowing the model to learn more effectively.\nFor machine learning, every dataset does not require normalization. It is required only when features have different ranges.\nFor example, consider a data set containing two features, age(x1), and income(x2). Where age ranges from 0–100, while income ranges from 0–20,000 and higher. Income is about 1,000 times larger than age and ranges from 20,000–500,000. So, these two features are in very different ranges. When we do further analysis, like multivariate linear regression, for example, the attributed income will intrinsically influence the result more due to its larger value. But this doesn’t necessarily mean it is more important as a predictor.\nBecause different features do not have similar ranges of values and hence gradients may end up taking a long time and can oscillate back and forth and take a long time before it can finally find its way to the global/local minimum. To overcome the model learning problem, we normalize the data. We make sure that the different features take on similar ranges of values so that gradient descents can converge more quickly.\nIn which cases, we don't need to normalize the data? It is generally a good idea to normalize your data when working with machine learning algorithms. Normalization can help improve the performance of some algorithms, and can also make it easier to compare different data sets. However, there may be some cases where normalization is not necessary. For example, if you are working with algorithms that are not sensitive to the scale of the data, or if the data is already in a normalized format, then normalization may not be necessary. Additionally, if you are working with data that has a natural ordinal relationship, such as grades or rankings, then normalization may not be necessary. It is always a good idea to evaluate your specific use case and data to determine if normalization is necessary.\nThere are several algorithms that are not sensitive to the scale of the data, and therefore may not require data normalization. Some examples of these algorithms include decision trees, random forests, and support vector machines with linear kernels. These algorithms are not sensitive to the scale of the data because they do not rely on distance measures to make predictions. In these cases, normalization may not be necessary, and could even be detrimental if it distorts the natural relationship between the features in the data. Again, it is always a good idea to evaluate your specific use case and data to determine if normalization is necessary.\nWhat's the difference between data normalization and standardization? Data normalization and data standardization are two techniques that are often used to pre-process data before it is used in machine learning algorithms. Both techniques are useful for transforming the data in a way that can improve the performance of the algorithms, but they are used for different purposes.\nData normalization is a technique that is used to scale the data so that it is within a specific range, such as 0 to 1. This is done by subtracting the minimum value from each data point and then dividing by the range of the data (the maximum value minus the minimum value). This transformation can help improve the performance of some machine learning algorithms, particularly those that use distance measures, because it ensures that all of the data is on the same scale.\nData standardization, on the other hand, is a technique that is used to transform the data so that it has a mean of 0 and a standard deviation of 1. This is done by subtracting the mean from each data point and then dividing by the standard deviation. This transformation can also help improve the performance of some machine learning algorithms, particularly those that are sensitive to the scale of the data.\nWhen to use data normalization and when to use data standardization? As a general rule, data normalization is a good technique to use when you want to scale the data to a specific range, such as 0 to 1. This can be useful for algorithms that are sensitive to the scale of the data, such as algorithms that use distance measures. Data standardization, on the other hand, is a good technique to use when you want to transform the data so that it has a mean of 0 and a standard deviation of 1. This can be useful for algorithms that are sensitive to the distribution of the data, such as algorithms that assume that the data is normally distributed.\nNormalization -\u0026gt; Data distribution is not Gaussian (bell curve). Typically applies in KNN, ANN\nStandardization -\u0026gt; Data distribution is Gaussian (bell curve). Typically applies in Linear regression, logistic regression.\nNote: Algorithms like Random Forest (any tree based algorithm) does not require feature scaling.\nWhat are some dimensionality reduction algorithms? Dimensionality reduction is a technique used to reduce the number of features in a data set, while retaining as much of the relevant information as possible. There are many different algorithms that can be used for dimensionality reduction, and the appropriate algorithm to use will depend on the specific characteristics of the data and the goals of the analysis. Some of the most common dimensionality reduction algorithms include:\nPrincipal Component Analysis (PCA): PCA is a linear dimensionality reduction algorithm that projects the data onto a lower-dimensional space by maximizing the variance of the data along the principal components. This can be useful for reducing the number of features in the data while retaining as much of the original information as possible. Singular Value Decomposition (SVD): SVD is a matrix factorization technique that can be used for dimensionality reduction. It decomposes the data matrix into three matrices, which can then be used to project the data onto a lower-dimensional space. Linear Discriminant Analysis (LDA): LDA is a supervised dimensionality reduction algorithm that projects the data onto a lower-dimensional space by maximizing the separation between different classes in the data. This can be useful for improving the performance of classification algorithms. t-distributed Stochastic Neighbor Embedding (t-SNE): t-SNE is a non-linear dimensionality reduction algorithm that projects the data onto a lower-dimensional space by preserving the local structure of the data. This can be useful for visualizing high-dimensional data and for uncovering patterns in the data. Explain Confusion Matrix A confusion matrix is a table that is often used to describe the performance of a classification algorithm. It provides a detailed breakdown of the correct and incorrect predictions made by the algorithm, allowing you to see how well the algorithm is performing and where it might be making mistakes.\nA confusion matrix has four main elements: true positives, true negatives, false positives, and false negatives. True positives are the number of correct predictions that the algorithm made for the positive class. True negatives are the number of correct predictions that the algorithm made for the negative class. False positives are the number of incorrect predictions that the algorithm made for the positive class (i.e. it predicted that the sample was positive, but it was actually negative). False negatives are the number of incorrect predictions that the algorithm made for the negative class (i.e. it predicted that the sample was negative, but it was actually positive).\nDifferent evaluation metric calculation Difference among micro, macro, weighted f1-score Excellent explanation: medium When to use Precision vs Recall vs f1-score F1-score When deciding which metric to use, you need to consider the specific goals of your analysis and the potential consequences of false positive and false negative predictions. If you want to minimize false positives, then you should use precision as a metric. If you want to minimize false negatives, then you should use recall as a metric. If avoiding both false positives and false negatives are equally important, then you should use the f1 score as a metric, which is the harmonic mean of precision and recall.\nPrecision In some cases, it may be more important to avoid false positives than false negatives. For example, if you are building an AI system to identify criminals in a housing society, then you want to avoid arresting innocent people (false positives), because this could lead to injustice. In this case, you should optimize your model using precision as a metric.\nRecall In other cases, it may be more important to avoid false negatives than false positives. For example, if you are building a security system to screen people for weapons at an airport, then you want to avoid letting dangerous people onto the plane (false negatives), because this could compromise the safety of passengers. In this case, you should optimize your model using recall as a metric.\nWhen to use F1 as a evaluation metric? The F1 score is a metric that is commonly used to evaluate the performance of a classification model. It is the harmonic mean of the model's precision and recall, which are both calculated by taking the number of true positive predictions by the model and dividing it by the total number of positive predictions made by the model. This means that the F1 score takes into account both the number of false positives and false negatives that the model produces.\nOne advantage of using the F1 score is that it is a balanced metric, which means that it considers both precision and recall equally. This is useful when you want to avoid a model that has a high precision but low recall, or vice versa. For example, in a medical diagnosis scenario, a model with high precision but low recall may not be useful because it may miss many cases of the disease that it is trying to detect.\nWhen to use AUC-ROC as an evaluation metric? The AUC-ROC (area under the receiver operating characteristic curve) is a metric that is commonly used to evaluate the performance of a binary classification model. It measures the ability of the model to distinguish between the positive and negative classes.\nOne advantage of using the AUC-ROC metric is that it is independent of the classification threshold, which means that it is not affected by changes in the threshold used to make predictions. This is useful when you want to compare the performance of different models on the same dataset, or when you want to compare the performance of the same model on different datasets.\nAnother advantage of the AUC-ROC metric is that it is not sensitive to class imbalance, which means that it can be used when there are unequal numbers of positive and negative instances in the dataset. This is useful when you are working with datasets that have imbalanced classes.\nWhat are the differences between Random Forest and Gradient Boosting? Random Forest and Gradient Boosting are two popular ensemble learning methods that are used for supervised learning tasks, such as classification and regression. Both methods use multiple decision trees to make predictions, but they differ in the way that the trees are trained and combined.\nOne key difference between Random Forest and Gradient Boosting is the way that the trees are trained. In Random Forest, the trees are trained independently using a random subsample of the training data. In contrast, in Gradient Boosting, the trees are trained sequentially, with each tree trying to correct the mistakes of the previous tree. This means that the trees in a Gradient Boosting model are more correlated than the trees in a Random Forest model.\nAnother key difference is the way that the trees are combined to make predictions. In Random Forest, the predictions of all the trees are combined using a majority vote. This means that the final prediction is the class that is predicted by the majority of the trees. In contrast, in Gradient Boosting, the predictions of the trees are combined using a weighted average, where the weights are determined by the performance of each tree.\nIn general, Random Forest is a good choice for tasks where the goal is to build a robust and accurate model with a low degree of overfitting. It is also a good choice when you have a large number of features in your dataset. In contrast, Gradient Boosting is a good choice for tasks where the goal is to build a highly accurate model, even at the cost of some overfitting.\nWhat's the difference between loss function and cost function? In machine learning, a loss function and a cost function are similar but distinct concepts. A loss function is a measure of how well a model is able to predict the true values of the target variable given the input data. It quantifies the error between the predicted values and the true values, and is used to guide the training of the model.\nIn contrast, a cost function is a measure of how well the model is able to make predictions on new data, given the training data. It is a function of the model's parameters, and is used to evaluate the performance of the model.\nIn other words, a loss function is used to measure the performance of a model on a given training dataset, while a cost function is used to evaluate the performance of the model on unseen data. The loss function is used to update the model's parameters during training, while the cost function is used to compare the performance of different models or the same model with different parameter settings.\nIn summary,\nThe loss function is to capture the difference between the actual and predicted values for a single record Whereas cost functions aggregate the difference for the entire training dataset. To do this it aggregates the loss values that are calculated per observation. A loss function is a part of a cost function.\nHow do you evaluate the performance of a machine learning model? There are several ways to evaluate the performance of a machine learning model, including:\nMeasuring the model's accuracy: This involves calculating the proportion of correct predictions made by the model on a test dataset. This is a good measure of performance for classification problems, but can be less reliable for regression problems. Calculating the model's error: This involves calculating the difference between the predicted values and the true values on the test dataset. This can be done using metrics such as the mean squared error (MSE) for regression problems, or the cross-entropy loss for classification problems. Using metrics specific to the type of problem: For example, in a classification problem, metrics such as precision, recall, and F1 score can be used to evaluate the model's performance. In a clustering problem, metrics such as the silhouette score or the Calinski-Harabasz index can be used to evaluate the model's performance. Visualizing the model's predictions: This involves creating plots such as scatter plots or histograms to compare the predicted values and the true values. This can help identify patterns and trends in the data and assess the model's performance. Overall, the choice of evaluation metrics will depend on the specific problem and the goals of the model. It is important to select evaluation metrics that are appropriate for the task and that align with the model's intended use.\nCan you describe the concept of regularization and how it can be used to prevent overfitting? Regularization is a technique used in machine learning to prevent overfitting by adding a penalty term to the loss function of a model. This penalty term, called the regularization term, is typically added to the loss function in the form of a weighted sum of the model's parameters, where the weights are chosen such that large parameter values are penalized more heavily than small ones. This serves to reduce the complexity of the model, which in turn helps to prevent overfitting by limiting the ability of the model to fit the noise in the training data. There are several different types of regularization that can be used, including L1 regularization, L2 regularization, and elastic net regularization.\nWhat is the difference between L1 regularization and L2 regularization L1 regularization is a technique used in machine learning to prevent overfitting by adding a regularization term to the loss function of a model. The regularization term is the sum of the absolute values of the model's parameters, multiplied by a constant called the regularization parameter. This can be written mathematically as follows:\n1L1 regularization term = regularization_parameter * sum(|parameters|) where regularization_parameter is a hyperparameter that determines the strength of the regularization, and parameters is a vector of the model's parameters.\nL2 regularization is another technique used to prevent overfitting by adding a regularization term to the loss function. In L2 regularization, the regularization term is the sum of the squares of the model's parameters, multiplied by a constant called the regularization parameter. This can be written mathematically as follows:\n1L2 regularization term = regularization_parameter * sum(parameters^2) where regularization_parameter is a hyperparameter that determines the strength of the regularization, and parameters is a vector of the model's parameters.\nBoth L1 and L2 regularization are used to reduce the complexity of a model and prevent overfitting, but they do so in different ways.\nL1 regularization encourages the model to use only a subset of its features.\nWhile L2 regularization discourages the model from using very large parameter values.\nHow do you handle missing or incorrect data in your data science project? There are several approaches that can be used to handle missing data in a data science project, depending on the specific needs of the project and the goals of the analysis. Some common approaches include:\nRemoving rows or columns that contain missing data: This approach can be useful if the missing data is not representative of the overall dataset, or if the amount of missing data is relatively small.\nImputing the missing data using a statistical method: This approach can be useful if the missing data is not random, and if there is a clear pattern or relationship between the missing data and other values in the dataset.\nUsing data from a different source to fill in the missing data: This approach can be useful if there is another dataset that contains information that is relevant to the missing data, and if it is possible to combine the two datasets in a meaningful way.\nIgnoring the missing data and proceeding with the analysis using only the available data: This approach can be useful if the amount of missing data is relatively small, and if it is not likely to significantly impact the results of the analysis.\nIf there are outliers in the data, we can replace the missing data with median of the feature.\nBetter way would be to use KNN to find the similar observations/samples, and then replace missing values with their (similar samples) average.\nKNN works better for numerical data.\nHow do you handle large datasets? Sampling: This involves selecting a representative subset of the data to work with, rather than using the entire dataset. This can be useful if the dataset is too large to work with efficiently, or if the patterns and trends in the data can be accurately represented by a smaller sample. Parallel processing: This involves using multiple computers or processors to perform the analysis simultaneously, rather than using a single processor. This can be useful if the dataset is too large to fit into the memory of a single computer, or if the analysis requires a lot of computational power. Data reduction: This involves applying techniques such as feature selection or dimensionality reduction to reduce the number of variables or features in the dataset. This can be useful if the dataset contains a large number of redundant or irrelevant variables, or if the analysis can be performed more efficiently with a smaller number of variables. Data partitioning: This involves dividing the dataset into smaller subsets and performing the analysis on each subset separately. This can be useful if the dataset is too large to work with efficiently, or if the analysis can be performed more efficiently in smaller chunks. How do you stay up-to-date with the latest developments in data science and machine learning? There are several ways to stay up-to-date with the latest developments in data science and machine learning. Some common approaches include:\nFollowing notable people on Twitter who are working with AI/ML technologies. They usually share a lot of news about new trends in the industry and academia. Reading books and articles on data science and machine learning: This can help you stay current with the latest theories, techniques, and applications in the field. Attending conferences and workshops: This can provide you with opportunities to learn from experts in the field, and to network with other professionals working in data science and machine learning. Joining online communities and forums: This can provide you with access to a wealth of knowledge and resources, and can also provide opportunities to connect with other data scientists and machine learning professionals. Participating in online courses and training programs: This can provide you with structured learning experiences, and can also help you stay up-to-date with the latest tools and technologies in the field. Staying current with industry news and trends: This can help you stay informed about the latest developments and innovations in the field, and can also provide valuable insights into how data science and machine learning are being used in the real world. How dimensionality reduction work in Machine Learning Dimensionality reduction is a technique that is used in machine learning to reduce the number of features or dimensions in a dataset. This is useful because it can make the data easier to work with and analyze, and can also improve the performance of machine learning algorithms.\nThere are several ways that dimensionality reduction can be implemented in machine learning, including:\nFeature selection: This involves selecting a subset of the most important features from the dataset, and removing the others. This can be useful if the dataset contains a large number of redundant or irrelevant features, or if the analysis can be performed more efficiently with a smaller number of features. Principal component analysis (PCA): This is a statistical technique that uses linear algebra to transform the data into a new space with fewer dimensions, while preserving as much of the original variance in the data as possible. This can be useful if the data is highly correlated, or if there is a strong linear relationship between the features. Autoencoders: These are artificial neural networks that are trained to learn a compact representation of the data, by encoding the data into a lower-dimensional space and then decoding it back into the original space. This can be useful if the data is non-linear, or if there is a complex relationship between the features. How PCA works? Principal component analysis (PCA) is a statistical technique that is often used for dimensionality reduction in machine learning. It is a method that uses linear algebra to transform the data into a new space with fewer dimensions, while preserving as much of the original variance in the data as possible.\nHere is a step-by-step explanation of how PCA works for dimensionality reduction:\nStandardize the data: The first step is to standardize the data by subtracting the mean from each feature and dividing by the standard deviation. This is necessary because PCA is sensitive to the scale of the data, and standardizing the data ensures that all the features are on the same scale. Compute the covariance matrix: The next step is to compute the covariance matrix of the standardized data. This is a square matrix that contains the pairwise covariances between all the features in the data. Compute the eigenvectors and eigenvalues: The next step is to compute the eigenvectors and eigenvalues of the covariance matrix. The eigenvectors are the directions in the data space along which the data varies the most, and the eigenvalues are the corresponding magnitudes of the variations. Select the eigenvectors with the highest eigenvalues: The next step is to select the eigenvectors with the highest eigenvalues, as these are the directions in the data space that capture the most variance. Transform the data into the new space: The final step is to transform the data into the new space defined by the selected eigenvectors. This is done by computing the dot product of the standardized data and the eigenvectors, which projects the data onto the new space. By using PCA for dimensionality reduction, data scientists and machine learning professionals can reduce the complexity of the data, and improve the performance of machine learning algorithms. Additionally, PCA can also be used to visualize high-dimensional data in a lower-dimensional space, which can help to gain insights into the underlying structure of the data.\nWhat are the different types of data distribution in statistics? In statistics, data can be distributed in many different ways, depending on the characteristics of the data and the underlying population. Some common distributions of data include:\nNormal distribution: This is a symmetrical distribution that is often described as a bell-shaped curve. It is commonly used to model data that is continuous and normally distributed, such as height, weight, or IQ. Binomial distribution: This is a distribution that is used to model data that can take on only two possible values, such as success or failure, or heads or tails. It is commonly used to model the probability of a certain number of successes in a given number of trials. Poisson distribution: This is a distribution that is used to model data that represents the number of events that occur in a given time or space. It is commonly used to model data that is discrete and counts the number of occurrences of an event, such as the number of accidents on a highway or the number of defects in a manufacturing process. Exponential distribution: This is a distribution that is used to model data that is continuous and has a constant rate of change. It is commonly used to model data that represents the time between events, such as the time between arrivals at a bus stop or the time between failures of a machine. Uniform distribution: This is a distribution that is used to model data that is continuous and has an equal probability of occurring within a given range. It is commonly used to model data that is randomly generated, such as the results of a dice roll or a random number generator. What are some clustering algorithm in Machine Learning? Clustering is a technique that is used to group data points into clusters based on their similarity. This can be useful for a variety of applications, such as image segmentation, customer segmentation, and anomaly detection. Some common clustering algorithms include:\nK-means: This is a popular and widely-used clustering algorithm that is based on the idea of partitioning the data into a specified number of clusters, and then iteratively refining the cluster assignments until the clusters are as compact and well-separated as possible. Hierarchical clustering: This is a clustering algorithm that is based on the idea of building a hierarchy of clusters, where each cluster is split into smaller clusters until each data point belongs to a single-point cluster. DBSCAN: This is a clustering algorithm that is based on the idea of finding dense clusters of data points in the data space, and then expanding the clusters to include points that are nearby. Expectation-maximization (EM): This is a clustering algorithm that is based on the idea of fitting a mixture model to the data, where each component of the mixture represents a different cluster. What are the different feature selection procedures in Machine Learning? Correlation-based Feature Selection: This technique calculates the correlation between each feature and the target variable and only keeps the features with a high correlation. This can be useful for removing redundant features that add little predictive power to the model. Wrapper-based Feature Selection: This technique uses a predictive model to evaluate each feature's importance, then selects the features that improve the model's performance. This is a more computationally intensive method, but it can be effective for selecting the most relevant features. Embedded-based Feature Selection: This technique trains a predictive model and then uses the model's weights to determine each feature's importance. Features with high absolute weight are important and retained in the model. This is a good method for selecting useful features for making predictions. Recursive Feature Elimination (RFE): This technique recursively removes features, builds a model using the remaining features, and then evaluates the model's performance. The process is repeated until only the most relevant features are left. Principal Component Analysis (PCA): This technique projects the data onto a lower-dimensional space and selects the most important principal components for building the model. This can be useful for reducing the dimensionality of the data and removing irrelevant features. In general, the best approach for feature selection will depend on the specific dataset and the type of model being used. It is important to experiment with different methods to find the one that works best for your particular situation.\nHow to select features for a xgboost model? To select features for a xgboost model, you can use the SelectFromModel method, which is part of the scikit-learn library. This method allows you to specify a threshold for feature importance, and then automatically selects the features that meet or exceed that threshold.\n1import xgboost as xgb 2from sklearn.feature_selection import SelectFromModel 3 4# Train your xgboost model 5model = xgb.XGBClassifier() 6model.fit(X_train, y_train) 7 8# Use SelectFromModel to select features with a minimum importance value of 0.2 9selection = SelectFromModel(model, threshold=0.2) 10selected_features = selection.transform(X_train) 11 12# Train a new model using only the selected features 13new_model = xgb.XGBClassifier() 14new_model.fit(selected_features, y_train) The SelectFromModel method is used to select all of the features that have an importance value of at least 0.2, as determined by the trained xgboost model. These selected features are then used to train a new xgboost model.\nYou can adjust the threshold value to select more or fewer features, depending on your needs. It's generally a good idea to use a relatively low threshold value, to ensure that you are selecting as many relevant features as possible. However, if your dataset has a large number of features and you want to reduce the number of features for computational efficiency, you can use a higher threshold value to select only the most important features.\nHow Neural Networks work? Neural networks are a type of machine learning algorithm that are inspired by the structure and function of the human brain. They are composed of many interconnected processing units, called neurons, that are arranged into layers. The neurons in the input layer receive input data, and the neurons in the output layer produce the final output of the network. The neurons in the hidden layers process the data and pass it on to the next layer.\nStep-by-step explanation of how a neural network works:\nInitialize the weights: The first step is to initialize the weights of the connections between the neurons in the network. The weights are typically initialized to small random values, in order to break any symmetry in the network and allow the network to learn from the data.\nFeed the input data through the network: The next step is to feed the input data through the network, by passing the data from the input layer to the first hidden layer. At each layer, the neurons compute a weighted sum of the inputs, and then apply an activation function to the sum in order to produce an output.\nPropagate the output through the network: The next step is to propagate the output of each layer through the network, by passing the output from one layer to the next. This continues until the output of the final layer is produced, which represents the final output of the network.\nCalculate the error: The next step is to calculate the error between the actual output of the network and the desired output. This error is used to measure the performance of the network and to guide the learning process.\nAdjust the weights: The final step is to adjust the weights of the connections between the neurons in the network in order to reduce the error. This is typically done using a gradient descent algorithm, which computes the gradient of the error with respect to the weights and updates the weights in the direction that reduces the error.\nBy repeating these steps, the neural network can learn from the data and improve its performance over time. As the network learns, the weights of the connections between the neurons are adjusted in order to capture the underlying patterns and relationships in the data. This allows the network to make accurate predictions and decisions based on the input data.\nHow the Backpropagation algorithm works Backpropagation is an algorithm used to train neural networks. It is used to compute the gradients of the loss function with respect to the weights of the network, so that the weights can be updated to minimize the loss.\nThe process of backpropagation can be broken down into the following steps:\nForward propagation: During forward propagation, the inputs are passed through the network and the predictions are made. The prediction error is then calculated using the loss function.\nBackward propagation: During backward propagation, the error is backpropagated through the network, starting at the output layer and working backwards layer by layer. The error at each layer is used to calculate the gradients of the loss function with respect to the weights of the network.\nWeight update: Once the gradients of the loss function with respect to the weights have been calculated, the weights can be updated using an optimization algorithm, such as stochastic gradient descent (SGD).\nRepeat: The process of forward propagation, backward propagation, and weight update is repeated until the loss function is minimized.\nBackpropagation is an efficient way to calculate the gradients of the loss function with respect to the weights of the network, which makes it an important algorithm in the training of neural networks.\nWhat is Gradient Descent, and what are the different versions of Gradient Descent Gradient descent is an optimization algorithm that is used to minimize a loss function. It works by adjusting the parameters of a model in small increments to minimize the loss.\nImagine you are at the top of a mountain and you want to find the path that leads to the bottom of the mountain. The bottom of the mountain represents the minimum of the loss function, and the parameters of the model are like your position on the mountain. The gradient of the loss function with respect to the parameters is like the slope of the mountain at your position. To find the minimum of the loss function, you can follow the direction of the gradient downhill until you reach the bottom of the mountain.\nThis is an oversimplification, but it gives the basic idea of how gradient descent works. In practice, the algorithm starts with an initial set of parameter values and then iteratively adjusts the values in the direction that reduces the loss. The magnitude of the update to the parameters is determined by the learning rate. The process is repeated until the loss function is minimized or a maximum number of iterations is reached.\nGradient descent is a widely used optimization algorithm in machine learning and is commonly used to train neural networks. There are several variations of gradient descent, such as batch gradient descent, stochastic gradient descent, and mini-batch gradient descent, which are used in different situations.\nBatch gradient descent: In batch gradient descent, the gradient is calculated for the entire training set and the parameters are updated all at once. This can be computationally expensive when the training set is large, but it is generally the most accurate method.\nStochastic gradient descent: In stochastic gradient descent, the gradient is calculated for each training example and the parameters are updated after each example. This can be faster than batch gradient descent because the update is performed after each example, but it can also be less stable because the updates are based on a single example.\nMini-batch gradient descent: In mini-batch gradient descent, the gradient is calculated for a small batch of training examples and the parameters are updated after each batch. This can be faster than batch gradient descent because the update is performed after a small number of examples, but it can also be less stable because the updates are based on a small number of examples.\nAccelerated gradient descent: There are several variants of gradient descent that use techniques such as momentum and Nesterov acceleration to improve the convergence rate of the algorithm. These techniques can help the algorithm escape from local minima and converge to the global minimum more quickly.\nWhich version of gradient descent to use depends on the problem at hand and the available computational resources. In general, mini-batch gradient descent is a good compromise between the speed of stochastic gradient descent and the stability of batch gradient descent.\nExplain different different types of layers in a Neural Network Fully Connected Layers: These are like a basic building block of a neural network that helps it learn patterns in the data. Each neuron in a layer is connected to every neuron in next layer, allowing the network to learn complex relationships between the inputs and outputs.\nConvolutional Layers: These layers are used in image and video recognition tasks. They help the network to recognize patterns in the image, such as edges, shapes, or textures, by applying a filter to the image and looking for specific features.\nPooling Layers: These layers are used to reduce the size of the image or feature map that comes out of a convolutional layer. Max pooling is a common type of pooling layer that takes the maximum value of a set of values in the feature map.\nRecurrent Layers: These layers are used to process sequential data, such as text or time-series data. They allow the network to remember information from previous time steps, which can be helpful in predicting the next value in a sequence.\nNormalization Layers: These layers are used to normalize the output of the previous layer to improve the stability and performance of the network. They make sure that the data going into the next layer is on the same scale and is easier to process.\nDropout Layers: These layers are used to prevent overfitting, which happens when the network becomes too specialized to the training data and doesn't generalize well to new data. Dropout randomly drops out some of the neurons in a layer during training, which makes the network more robust to noise and variation in the data.\nEmbedding Layers: These layers are used to represent categorical data, such as words or user IDs, as continuous vectors that can be fed into the neural network. This makes it easier for the network to process and learn patterns in the data.\nActivation Layers: These layers apply a nonlinear function to the output of the previous layer, introducing nonlinearity into the model. This allows the network to learn complex relationships between the inputs and outputs that wouldn't be possible with a linear model.\nSelf-Attention Layers: These layers are commonly used in transformer-based models such as BERT or GPT-3. They allow the model to attend to different parts of the input sequence when processing each token.\nSkip-Connection Layers: These layers allow the network to skip over one or more layers during training. This can help to prevent the vanishing gradient problem and improve the performance of the network.\nSpatial Transformer Layers: These layers allow the network to learn to transform the input data in a spatially-varying way. This can be useful in tasks such as image segmentation, where the network needs to learn to focus on different parts of the image.\nResidual Layers: These layers are used in residual networks (ResNets) to allow the network to learn residual functions. This can help to prevent the vanishing gradient problem and improve the performance of the network.\nCapsule Layers: These layers are used in capsule networks to allow the network to learn structured representations of objects. This can be useful in tasks such as object recognition or pose estimation.\nWhat are the building blocks of a Convolutional Neural Networks (CNNs) Convolutional neural networks (CNNs) are a type of neural network that is specifically designed to work with data that has a grid-like structure, such as an image. CNNs are comp0sed of multiple layers of interconnected neurons, where the neurons in each layer are arranged in a three-dimensional grid. The building blocks of a CNN include:\nInput layer: The input layer receivess the input data and passes it on to the first hidden layer. In the case of an image, the input layer consists of multiple neurons, each representing a pixel in the image.\nHidden layers: The hidden layers are composed of multiple neurons arranged in a three-dimensional grid. Each neuron in a hidden layer receives inputs from a small region of the previous layer, and produces an output that is passed on to the next layer.\nConvolutional layers: In a CNN, the hidden layers typically include convolutional layers, where the neurons perform a convolution operation on the input data. This involves applying a small kernel or filter to the input data, which extracts features from the data and passes them on to the next layer.\nPooling layers: The hidden layers of a CNN may also include pooling layers, where the neurons perform a down-sampling operation on the input data. This involves summarizing the input data in some way, such as taking the maximum or average value, in order to reduce the dimensionality of the data and make the network more robust to changes in the input data.\nFully-connected layers: The final hidden layers of a CNN are typically fully-connected, where each neuron receives inputs from all the neurons in the previous layer. This allows the network to combine the extracted features from the convolutional and pooling layers, and make a prediction or decision based on the input data.\n**Batch Normalization layers **: Batch Normalization is a technique for training deep neural networks that standardizes the inputs to a layer for each mini-batch. It has the effect of stabilizing the learning process and dramatically reducing the number of epochs required to train a deep learning model. Batch normalization works by normalizing the activations of a layer for each mini-batch. This has the effect of making the distribution of the activations more stable, which in turn makes it easier to train the model. It also has the effect of regularizing the model, which can reduce overfitting.\nOutput layer: The output layer produces the final output of the network. In the case of an image classification task, the output layer may consist of multiple neurons, each representing a different class. The output of the network is the predicted class of the input image.\nExplain Stride in CNN In a Convolutional Neural Network (CNN), the stride is the number of pixels that the convolutional filter moves each time it is applied to the input. The stride is a hyperparameter of the CNN, and it can be adjusted to control the size of the output produced by the convolutional layer.\nFor example, consider a CNN with an input of size 32x32 and a convolutional layer with a kernel size of 3 and a stride of 2. This means that the convolutional filter will be applied to the input in a sliding window fashion, moving 2 pixels at a time. The output of this convolutional layer will be 16x16, since the filter is applied to every other pixel in the input.\nIncreasing the stride can reduce the size of the output produced by the convolutional layer, which can help to reduce the number of parameters in the model and improve the training process. However, it can also reduce the amount of information that is captured by the convolutional layer, which can degrade the performance of the model.\nHow to calculate the number of parameters and output shape size for CNN? What are some of the state-of-the-art Computer Vision models ResNet: This is a deep convolutional neural network that is trained on large datasets and is capable of achieving high accuracy on many tasks. DenseNet: This is another deep convolutional neural network that is known for its ability to efficiently learn complex representations. Inception: This is a model that uses a combination of convolutional and pooling layers to learn features from images. Mask R-CNN: This is a model that is specifically designed for object detection and instance segmentation, which involves identifying and segmenting individual objects in an image. GANs: Generative adversarial networks are a class of models that can be used to generate new images based on a given input. YOLO (You Only Look Once): This is a fast object detection model that can be used to identify objects in real-time. R-CNN (Regional Convolutional Neural Network): This is a model that uses region proposal algorithms to identify objects in an image and then uses a CNN to classify the objects. SSD (Single Shot Detector): This is a model that combines a CNN with a regression layer to identify objects in an image. U-Net: This is a model that is specifically designed for image segmentation, which involves dividing an image into multiple segments or regions. VGG (Visual Geometry Group): This is a model that uses a series of convolutional and pooling layers to learn features from images. How a Recurrent Neural Network (RNN) works An RNN is a type of neural network that is designed to process sequential data. It does this by using a \u0026quot;memory\u0026quot; that allows it to remember important information from the past, which it can use to inform its processing of the current input.\nThe basic building block of an RNN is the \u0026quot;recurrent neuron,\u0026quot; which has a single input and a single output, but it also has a \u0026quot;memory\u0026quot; in the form of a hidden state. The hidden state is a vector of values that is maintained by the neuron and is used to store information from the past.\nAt each time step, the recurrent neuron receives an input and combines it with its current hidden state to produce an output and a new hidden state. The output and the new hidden state are then used as input for the next time step. This allows the RNN to maintain a \u0026quot;memory\u0026quot; of the input data and use it to inform its processing of the current input.\nRNNs can have many layers, and each layer consists of a set of recurrent neurons. The input data is passed through the layers of the RNN in a sequential manner, with the output of each time step being used as input for the next time step. This allows the RNN to capture patterns and dependencies over longer time periods.\nRNNs are trained using a variant of gradient descent, such as mini-batch gradient descent or stochastic gradient descent. During training, the weights of the recurrent neurons are adjusted to minimize the error between the predicted output and the ground truth.\nRNNs are widely used in natural language processing tasks, such as language translation, language generation, and text classification.\nThere are several types of RNNs, including:\nSimple RNNs: These are the simplest type of RNN, and they have a single hidden layer that processes the input data sequentially. Long Short-Term Memory (LSTM) Networks: These are a more advanced type of RNN that have additional gates that control the flow of information through the network. LSTMs are particularly useful for tasks that require the network to remember information over long periods of time. Gated Recurrent Units (GRUs): These are another type of advanced RNN that have a similar structure to LSTMs, but they have fewer parameters and are easier to train. Main difference between LSTM and GRU Long Short-Term Memory (LSTM) networks and Gated Recurrent Units (GRUs) are both types of Recurrent Neural Networks (RNNs) that are designed to process sequential data. They both have a \u0026quot;memory\u0026quot; that allows them to remember important information from the past, which they can use to inform their processing of the current input.\nThe main difference between LSTMs and GRUs is the way they maintain their \u0026quot;memory.\u0026quot; LSTMs have three different types of gates that control the flow of information through the network: the input gate, the output gate, and the forget gate. These gates allow LSTMs to selectively choose which information to remember and which to forget, which makes them very effective at learning long-term dependencies.\nGRUs, on the other hand, have a single update gate that controls the flow of information through the network. The update gate determines which information to retain from the previous hidden state and which to discard. GRUs are simpler than LSTMs and have fewer parameters, which makes them easier to train.\nBoth LSTMs and GRUs have been successful at a wide range of natural language processing tasks, such as language translation, language generation, and text classification. However, LSTMs tend to be more powerful and are generally considered to be the better choice for tasks that require the network to remember information over long periods of time. GRUs are a good choice when you want a simpler and faster model that is still able to capture long-term dependencies.\nWhat is the difference between batch prediction and online prediction? Batch prediction and online prediction are two different methods for making predictions using machine learning models.\nBatch prediction involves using a trained machine learning model to make predictions on a large batch of data all at once. This is typically done by feeding the entire dataset into the model, and then using the model to make predictions on each data point in the batch. Batch prediction is useful when the dataset is large and the predictions can be made in parallel, as it can be more efficient than making predictions one at a time.\nOnline prediction involves using a trained machine learning model to make predictions on individual data points as they are received. This is useful when the data is streaming or the predictions need to be made in real-time, as it allows the model to make predictions on the fly.\nGive a real-life example of when to use batch inference and when to use online inference Batch inference is typically used when you have a large amount of data that you need to process all at once, such as when you are running a machine learning model on a dataset to make predictions. Online inference, on the other hand, is used when you need to make predictions on individual data points in real-time, such as when you are using a speech-to-text model to transcribe audio in real-time.\nFor example, if you are building a system to classify images, you might use batch inference to process a dataset of images and train a machine learning model. Once the model is trained, you could then use online inference to classify new images as they come in, in real-time.\nAnother example might be a website that uses a machine learning model to recommend products to customers. In this case, you could use batch inference to process the entire catalog of products and train a recommendation model, and then use online inference to generate personalized recommendations for individual users as they browse the website.\nHow to reduce the prediction serving latency in Machine Learning? There are a few different ways to reduce the prediction serving latency in machine learning, including the following:\nOptimize the model for inference: One way to reduce the prediction serving latency is to optimize the model for inference. This can involve techniques such as quantizing the model to reduce the number of bits used to represent the weights, or pruning the model to remove redundant or unnecessary connections. Use a faster hardware platform: Another way to reduce the prediction serving latency is to use a faster hardware platform for serving the model. For example, you could use a high-performance GPU or a custom ASIC designed for machine learning inference to speed up the processing of predictions. Use a faster inference algorithm: Some machine learning models can be served using different inference algorithms, which can have different performance characteristics. Choosing a faster inference algorithm can help reduce the prediction serving latency. Use a cache: If your model is serving a large number of requests, it can be helpful to use a cache to store the results of previous predictions. This can allow you to quickly serve the same request multiple times without having to re-run the entire model. Use a distributed serving architecture: Finally, using a distributed serving architecture can also help reduce the prediction serving latency. This involves running multiple instances of the model on different machines, and using a load balancer to distribute incoming requests across the different instances. This can help reduce the time it takes to serve each individual request. Explain how pre-trained BERT embeddings are generated. Pretrained BERT embeddings are generated by training a BERT model on a large corpus of text data. The BERT model is a type of Transformer-based neural network that is designed to process and understand natural language. During training, the model learns to generate a numerical representation, or embedding, for each word in the training corpus. These word embeddings capture the semantic meaning of the words and can be used as input to other natural language processing models.\nHow do the pre-trained weights understand completely new unseen word representation? Pretrained BERT models are not specifically designed to understand completely new words that they have not seen during training. Instead, they rely on a process called subword tokenization, which breaks words down into smaller pieces called subwords. For example, the word \u0026quot;unexpected\u0026quot; might be broken down into the subwords \u0026quot;un\u0026quot;, \u0026quot;expect\u0026quot;, and \u0026quot;ed\u0026quot;. The BERT model can then generate an embedding for each subword, which can be combined to represent the overall meaning of the original word. This allows the model to generalize to words that it has not seen in the training data, by using the subwords that it has learned to represent similar words.\nWhat's the workflow of a text summarization in NLP using pre-trained weights? The workflow of a text summarization model using pretrained weights would generally involve the following steps:\nPreprocessing the input text data to clean and prepare it for input to the model. This may involve tokenizing the text, removing punctuation and stop words, and extracting important keywords and phrases. Loading the pretrained weights into the model, which would have been trained on a large corpus of text data. Feeding the preprocessed input text into the model, which would generate a numerical representation, or embedding, for each word in the text. Using the word embeddings as input to the summarization model, which would generate a summary of the input text. This summary may be a shorter version of the original text, or it may highlight the most important points in the text. Postprocessing the output summary to clean and format it, and outputting it in the desired format. This is a general outline of the process, and specific implementations may vary depending on the details of the model and the data.\nWhat are the different approaches to generating word embeddings in NLP? Word2Vec: This is a popular method for learning word embeddings by predicting the surrounding words in a sentence or phrase. GloVe: This method learns word embeddings by training a model to predict the co-occurrence of words in a corpus of text data. FastText: This method learns word embeddings by training a model to predict the words in a sentence, based on the characters in the words. BERT: This method uses a transformer-based neural network to learn word embeddings by training on a large corpus of text data. How to generate embeddings for a Computer Vision task? To generate embeddings for a computer vision task, you would typically use a convolutional neural network (CNN) to extract features from the input images. The CNN would be trained on a large dataset of images, and during training, it would learn to generate a numerical representation, or embedding, for each image. This embedding would capture the key features of the image, and could be used as input to other machine learning models for tasks such as image classification or object detection.\nTo generate the embeddings, you would first preprocess the input images by resizing them to a fixed size and converting them to a format that is suitable for input to the CNN. You would then feed the preprocessed images into the CNN, which would generate the embeddings. These embeddings could then be used as input to other machine learning models for downstream tasks.\nWhat's the difference between the BERT model and the SBERT model? BERT, or Bidirectional Encoder Representations from Transformers, is a type of transformer-based neural network that is designed to process and understand natural language. SBERT, or Sentence-BERT, is a variation of BERT that is specifically designed to encode sentences rather than individual words. This allows SBERT to capture the meaning of entire sentences, rather than just the individual words, which can be useful for certain natural language processing tasks such as sentiment analysis or text classification.\nOne key difference between the BERT and SBERT models is the input data that they are designed to process. BERT is typically trained on a large corpus of text data and is designed to generate word embeddings, which capture the semantic meaning of individual words. In contrast, SBERT is trained to generate sentence embeddings, which capture the meaning of entire sentences. This allows SBERT to better capture the context and meaning of sentences, which can be useful for certain natural language processing tasks.\nAnother key difference between the two models is their performance and accuracy. BERT is a highly accurate model, but it is designed to process individual words, so it may not always capture the meaning of longer phrases or sentences. SBERT, on the other hand, is specifically designed to process entire sentences, so it may be more accurate for tasks that require understanding the meaning of longer phrases or sentences. However, SBERT is a relatively new model, so its performance has not been extensively tested and evaluated.\nHow a video classification works There are many different architectures that can be used for video classification tasks, and the best architecture for a particular task will depend on the specific requirements and constraints of the task. Some commonly used architectures for video classification include:\nCNNs + LSTM: These are particularly well-suited for processing image data, and can be used to extract features from each frame of a video. CNNs can be combined with other types of neural networks, such as long short-term memory (LSTM) networks, to take into account the temporal dependencies between the frames of the video.\n3D convolutional neural networks (3D CNNs): These are similar to CNNs, but are designed to process data with a temporal dimension, such as video data. They are able to learn spatiotemporal features from the video data by applying convolutional filters to the data in three dimensions (i.e., width, height, and time).\nTwo-stream neural networks: These are a type of architecture that combines the outputs of a CNN that processes the raw video frames with the outputs of a CNN that processes optical flow maps of the video. Optical flow maps capture the motion between frames in a video, and can provide additional information about the motion and dynamics of the objects in the video.\nTemporal dependencies refer to the relationships between events or variables that are dependent on the time at which they occur. In the context of a video classifier, temporal dependencies refer to the relationships between the frames of the video and how they change over time.\nFor example, consider a video of a person walking. Each frame of the video represents a snapshot of the person's appearance at a particular point in time. The temporal dependencies between these frames would include the person's motion and the changes in their appearance as they walk. A model that is able to take into account these temporal dependencies would be able to use information from multiple frames in the video to better understand the person's actions and make a more informed classification decision.\nLSTM networks are particularly well-suited for modeling temporal dependencies because they are able to remember information from previous timesteps in a sequence and use this information to inform their decisions at later timesteps. This allows them to effectively model the changing relationships between the frames of a video over time.\nCNN+LSTM network A video classifier that uses a combination of convolutional neural networks (CNNs) and long short-term memory (LSTM) networks can work by taking in a video as input and processing it through the CNN portion of the model to extract features from each frame of the video. These features are then passed to the LSTM portion of the model, which takes into account the sequence of frames in the video and the temporal dependencies between them to make a classification decision.\nThe CNN portion of the model is responsible for learning features from the individual frames of the video that are relevant for the classification task. This is done using convolutional layers, which apply a set of filters to the input data and learn to recognize patterns and features within the data.\nThe LSTM portion of the model is a type of recurrent neural network that is designed to process sequential data. It is able to remember information from previous timesteps in the sequence and use this information to inform its classification decision.\nTogether, the CNN and LSTM portions of the model are able to take into account both the individual frames of the video and the temporal dependencies between them to make a classification decision. This allows the model to learn to recognize complex patterns and behaviors in the video data and make more accurate classification decisions.\n3D CNNs 3D convolutional neural networks (3D CNNs) are a type of neural network that is designed to process data with a temporal dimension, such as video data. They are particularly well-suited for tasks such as video classification, action recognition, and anomaly detection.\n3D CNNs are similar to traditional convolutional neural networks (CNNs), but are designed to apply convolutional filters to the data in three dimensions (i.e., width, height, and time). This allows them to learn spatiotemporal features from the data, which are features that capture both the spatial relationships between the pixels in an image and the temporal dependencies between the frames in a video.\nTo apply 3D convolutional filters to the data, 3D CNNs typically use a kernel that is three-dimensional (i.e., a cube) rather than a kernel that is two-dimensional (i.e., a square). This kernel is then moved across the data in all three dimensions (width, height, and time) and convolved with the data to produce a feature map. The process is then repeated using different kernels to learn multiple features from the data.\n3D CNNs typically consist of multiple layers of 3D convolutional filters, followed by non-linear activation functions, and may also include pooling layers and fully connected layers. The output of the final layer of the network can be used for tasks such as classification or regression.\n3D CNNs are able to learn complex spatiotemporal features from the data and can be trained to recognize patterns and behaviors in the data that are not easily detectable using other approaches. However, they can be computationally intensive to train and may require large amounts of data to achieve good performance.\nWhy do we use the activation functions? Activation functions are used in artificial neural networks to introduce non-linearity. Without activation functions, neural networks would be limited to linear models, which are not very powerful. Activation functions allow neural networks to model complex relationships between input and output.\nActivation functions also help normalize the output of a neuron so that it falls within a specific range, which can be useful for modeling probability or for creating stable and consistent models.\nThere are many different activation functions to choose from, and the choice of activation function can have a big impact on the performance of the neural network. Some commonly used activation functions include the sigmoid function, the tanh function, and the ReLU (Rectified Linear Unit) function.\nWhy ReLU works better than others? The ReLU (Rectified Linear Unit) activation function has become very popular in recent years because it has been shown to work well in a wide range of deep learning models. The ReLU function is defined as\n1f(x) = max(0, x) where x is the input to the activation function.\nThere are several reasons why the ReLU function has become so popular:\nIt is very simple to compute, requiring only a simple max operation. This makes it very efficient to compute, especially in large models where the activation function is called many times. It has been shown to work well in practice. It has been used in a wide range of models and has consistently produced good results. It is not saturating, meaning that the output of the function does not tend towards a lower or upper bound. This can improve the stability of the model and allow it to learn more effectively. It can alleviate the vanishing gradient problem, which is a common issue in deep learning models. The vanishing gradient problem occurs when the gradients of the parameters with respect to the loss function become very small, making it difficult for the model to learn. The ReLU function does not suffer from this issue because it does not saturate. Explain vanishing gradient problem As the backpropagation algorithm advances downwards(or backward) from the output layer towards the input layer, the gradients often get smaller and smaller and approach zero which eventually leaves the weights of the initial or lower layers nearly unchanged. As a result, the gradient descent never converges to the optimum. This is known as the *vanishing gradients* problem.\nWhy? Certain activation functions, like the sigmoid function, squishes a large input space into a small input space between 0 and 1. Therefore, a large change in the input of the sigmoid function will cause a small change in the output. Hence, the derivative becomes small.\nHowever, when n hidden layers use an activation like the sigmoid function, n small derivatives are multiplied together. Thus, the gradient decreases exponentially as we propagate down to the initial layers.\nSolution Use non-saturating activation function: because of the nature of sigmoid activation function, it starts saturating for larger inputs (negative or positive) came out to be a major reason behind the vanishing of gradients thus making it non-recommendable to use in the hidden layers of the network.\nSo to tackle the issue regarding the saturation of activation functions like sigmoid and tanh, we must use some other non-saturating functions like ReLu and its alternatives.\nProper weight initialization: There are different ways to initialize weights, for example, Xavier/Glorot initialization, Kaiming initializer etc. Keras API has default weight initializer for each types of layers. For example, see the available initializers for tf.keras in keras doc.\nYou can get the weights of a layer like below:\n1# tf.keras 2model.layers[1].get_weights() Residual networks are another solution, as they provide residual connections straight to earlier layers. Use smaller learning rate. Batch normalization (BN) layers can also resolve the issue. As stated before, the problem arises when a large input space is mapped to a small one, causing the derivatives to disappear. Batch normalization reduces this problem by simply normalizing the input, so it doesn’t reach the outer edges of the sigmoid function. 1# tf.keras 2 3from keras.layers.normalization import BatchNormalization 4 5# instantiate model 6model = Sequential() 7 8# The general use case is to use BN between the linear and non-linear layers in your network, 9# because it normalizes the input to your activation function, 10# though, it has some considerable debate about whether BN should be applied before 11# non-linearity of current layer or works best after the activation function. 12 13model.add(Dense(64, input_dim=14, init=\u0026#39;uniform\u0026#39;)) # linear layer 14model.add(BatchNormalization()) # BN 15model.add(Activation(\u0026#39;tanh\u0026#39;)) # non-linear layer Batch normalization applies a transformation that maintains the mean output close to 0 and the output standard deviation close to 1.\nHypothesis testing using P-value P value is the probability for the null hypothesis to be true.\nP-values are used in hypothesis testing to help decide whether to reject the null hypothesis. The smaller the p-value, the more likely you are to reject the null hypothesis.\nNull hypothesis: An assumption that treats everything same or equal. Let’s say, I have made an assumption that global GDP would be same before and after the covid pandemic, and that’s my null hypothesis. Now, using the GDP data, we can find the p-value and justify our null hypothesis.\nSteps:\nCollect data Define significance level; many cases it’s 0.05 Run some statistical test (given below). Now, let’s say, we have run the test on 100 countries and out p value is 0.05, it means our null hypothesis would be true for only 5 countries.\nStandard industry standard significance levels are:\n0.01 \u0026lt; p_value: very strong evidence against null hypothesis. 0.01 \u0026lt;= p_value \u0026lt; 0.05 : strong evidence against null hypothesis. 0.05 \u0026lt;= p_value \u0026lt; 0.10 : mild evidence against null hypothesis. p_value \u0026gt;= 0.10 : accept null hypothesis. There are different statistical tests for calculating p-value:\nZ-test T-Test Anova Chi-square P-value and Hypothesis testing Watch this video as well:\nExplain Autoencoders An autoencoder is a type of neural network that is used to learn a compressed representation of some data. It consists of two main components: an encoder and a decoder.\nThe encoder takes in the input data and converts it into a lower-dimensional representation, called the encoding. The encoding is typically much smaller than the original input, so it can be thought of as a compressed version of the input data.\nThe decoder takes the encoding and converts it back into the original data, as closely as possible. The goal of the autoencoder is to learn a good encoding that allows the decoder to reconstruct the original data with minimal loss of information.\nAutoencoders are used for a variety of tasks, such as dimensionality reduction, denoising, and feature learning. They can be useful for finding patterns in data and for creating more efficient and effective machine learning models.\nOne example of a real-life problem that can be solved using autoencoders is image denoising. Suppose you have a dataset of images that have been corrupted by noise, such as salt and pepper noise or Gaussian noise. You can train an autoencoder to remove the noise from the images by using the noisy images as the input and the clean images as the target output.\nDuring training, the encoder will learn to extract the important features from the images and compress them into a lower-dimensional encoding, while the decoder will learn to reconstruct the clean images from the encoding. After training, you can use the encoder and decoder separately to denoise new images by encoding them and then decoding the encoding. The idea is that the encoder has learned to extract the important features of the image and discard the noise, while the decoder has learned to reconstruct the clean image from those features.\nAnother example is anomaly detection in time series data. Suppose you have a dataset of time series data, such as sensor readings or stock prices, and you want to detect anomalies or unusual events in the data. You can train an autoencoder to model the normal behavior of the data, and then use the autoencoder to flag instances where the input data is significantly different from the normal behavior.\nCan we use sigmoid activation function as the last layer? Sigmoid is commonly used as the last layer of a model when the task is binary classification.\nA sigmoid function maps input values to output values between 0 and 1, and when the output is greater than 0.5, it is considered a positive class and when it is less than 0.5 it is considered a negative class. The output of the sigmoid function can be interpreted as the probability of the input belonging to the positive class.\nHowever, keep in mind that if you have multi-class problem you will have to use different approach like softmax .\nCan we use ReLU in the last layer The rectified linear unit (ReLU) activation function is commonly used in the hidden layers of a neural network, it returns the input if it is positive, and returns 0 if it is negative. It is a popular choice because it is computationally efficient and helps reduce the vanishing gradient problem, which can occur when training deep neural networks with other activation functions such as the sigmoid.\nFor the last layer of the neural network, it depends on the task you are trying to perform. ReLU can be used as the activation function in the last layer if the task is regression problem, as the output of the model will be continuous.\nFor example, if you want to predict the price of a house based on certain features, you could use a linear activation function for the last layer. The output of the network would be a continuous value, representing the predicted price of the house.\nBut if the task is classification problem, the most common activation functions for the last layer is softmax or sigmoid. If you have a multi-class problem you will use softmax, and if you have a binary classification problem you will use sigmoid.\nWhat's the difference between ReLU and Leaky ReLU The rectified linear unit (ReLU) and leaky rectified linear unit (Leaky ReLU) are both variants of the rectified linear unit (ReLU) activation function, which is commonly used in deep learning networks.\nThe standard ReLU activation function returns the input if it is positive, and returns 0 if it is negative. Mathematically, it can be represented as: f(x) = max(0, x)\nLeaky ReLU, on the other hand, is an improvement over the standard ReLU function, it addresses the issue of the \u0026quot;dying ReLU\u0026quot; problem. The dying ReLU problem occurs when a large number of neurons in a network are stuck in the \u0026quot;dead\u0026quot; state, meaning that they always output 0. This can happen when the input to the ReLU function is always negative, preventing the neuron from updating its weights and becoming active again.\nLeaky ReLU addresses this issue by allowing a small, non-zero gradient when the input is negative. The function is defined as: f(x) = max(αx, x) where α is a small positive constant, usually set to 0.01. This non-zero gradient allows the weights of the neurons to continue updating, even when the input is negative, avoiding the \u0026quot;dead\u0026quot; state and improving the network's performance.\nThe main difference between the two is that the ReLU activation function outputs 0 for any negative input, while the Leaky ReLU activation function outputs a small negative value (alpha * x) for any negative input. This small negative output allows the network to continue updating its weights, reducing the chance of getting stuck in a dead ReLU state and thus improving the model's performance.\nIn practice, Leaky ReLU is often found to perform better than the standard ReLU function in deep neural networks and is more commonly used, and there's also more advanced version of Leaky ReLU, such as parametric ReLU (PReLU), which allows to learn the value of the leakage coefficient during the training process, making it adaptive.\nKeras example of using LeakyReLU\n1from keras.layers import LeakyReLU, Dense 2 3dense_layer = Dense(128, activation=LeakyReLU(alpha=0.01)) Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/frequently-asked-data-science-interview-qestions/","section":"artificial-intelligence","tags":["deep learning","machine learning","data science"],"title":"Ace Your Data Science Interview - Top Questions With Answers"},{"body":"","link":"https://blog.sksoumik.com/tags/deep-learning/","section":"tags","tags":null,"title":"deep learning"},{"body":"Before jumping into Deep Learning, one must know the classical/traditional Machine Learning algorithms, because understanding traditional machine learning algorithms can provide a strong foundation in machine learning concepts. These algorithms often involve simple, intuitive concepts that can be helpful in understanding more complex deep learning models.\nTraditional machine learning algorithms can be faster to train and easier to interpret than deep learning models. This can be particularly useful in situations where you need to make quick decisions or where it's important to understand the reasoning behind a model's predictions. Traditional machine learning algorithms can be more effective for certain types of problems. For example, linear models like linear regression and logistic regression can be effective for problems with a small number of features, while decision trees and random forests can be effective for problems with a large number of features.\nDeep learning algorithms can be more difficult to learn and require a lot of data and computational resources to train effectively. By learning traditional machine learning algorithms first, you can get a sense of how different algorithms work and what types of problems they are best suited for, which can make it easier to decide when and how to use deep learning algorithms.\nLinear Regression Linear regression is a machine learning algorithm used for modeling the linear relationship between a dependent variable (also known as the target or output variable) and one or more independent variables (also known as the features or input variables). It is used to make predictions about the value of the dependent variable based on the values of the independent variables.\nIn univariate linear regression, there is only one independent variable, while in multivariate linear regression, there are multiple independent variables (also known as features).\nThe line that is fit to the data in linear regression is defined by the equation\n1y = mx + b where y is the dependent variable, x is the independent variable, m is the slope of the line, and b is the y-intercept (the point where the line crosses the y-axis).\nIn the context of machine learning, this equation can be written as\n1h(X) = W0 + W1.X where W0 and w1 are weights, X is the input feature, and h(x) is the label (i.e. y-value).\nThe goal of linear regression is to find the weights (W0 and w1) that lead to the best-fitting line for the input data. The best-fitting line is determined in terms of the lowest cost, which is a measure of how far off the model's predictions are from the actual training data. Linear Regression, The Mean Squared Error (MSE) loss function is used usually.\nEssentially, the MSE measures the average of the squared residuals (the difference between actual and predicted values). To get a more intuitive understanding, let’s dive deeper into what each variable means.\nY = the actual data point ŷ (pronounced y hat) = the predicted data point n = the total amount of data points Training a linear regression model involves using a learning algorithm to find the weights (w0 and w1) that minimize the cost. One common algorithm used for this purpose is gradient descent, which involves iteratively updating the values of w0 and w1 in the direction that minimizes the cost. The algorithm follows the following pseudo-code:\n1Repeat until convergence { 2 temp0 := W0 - a.((d/dW0) J(W0,W1)) 3 temp1 := W1 - a.((d/dW1) J(W0,W1)) 4 W0 = temp0 5 W1 = temp1 6} Where (d/dW0) and (d/dW1) are the partial derivatives of J(W0,W1) with respect to W0 and W1, respectively. The gist of this partial differentiation is basically the derivatives:\n1(d/dW0) J(W0,W1) = W0 + W1.X - T 2(d/dW1) j(W0,W1) = (W0 + W1.X - T).X If we run the gradient descent learning algorithm on the model, the model will converge to a minimum cost. The weights that led to that minimum cost are used as the final values for the line function h(x) = w0 + w1x, which is the linear regressor.\nOnce the model is trained, it can be used to make predictions about the dependent variable for new data by plugging in the values of the independent variables into the equation of the line. The performance of the model can be evaluated by comparing the predicted values to the actual values of the dependent variable in.\nRead more about the different types of regression models from here in this blog.\nImplementation\n1from sklearn.model_selection import train_test_split 2from sklearn.linear_model import LinearRegression 3from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score 4 5 6 7# Split the data into training and test sets 8X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 9 10# Scale the features (optional) 11from sklearn.preprocessing import StandardScaler 12scaler = StandardScaler() 13X_train = scaler.fit_transform(X_train) 14X_test = scaler.transform(X_test) 15 16 17# Create the linear regression model 18model = LinearRegression() 19 20# Fit the model to the training data 21model.fit(X_train, y_train) 22 23# Make predictions on the test data 24y_pred = model.predict(X_test) 25 26# Calculate the mean absolute error (MAE) 27mae = mean_absolute_error(y_test, y_pred) 28 29# Calculate the mean squared error (MSE) 30mse = mean_squared_error(y_test, y_pred) 31 32# Calculate the root mean squared error (RMSE) 33rmse = np.sqrt(mse) 34 35# Calculate the R2 score 36r2 = r2_score(y_test, y_pred) Polynomial Regression Polynomial regression is a type of regression analysis in which the relationship between the independent variable x and the dependent variable y is modeled as an n-th degree polynomial. It is used to model relationships between variables that are not linear.\nFig: Linear Regression [3]\nFig: Polynomial Regression [3]\nPolynomial regression is a type of regression analysis in which the relationship between the independent variable x and the dependent variable y is modeled as an nth degree polynomial. Polynomial regression can be used to model relationships between variables that are not linear.\nLinear regression models the relationship between two variables using a straight line. But sometimes the relationship between two variables is more complex, and a straight line is not the best way to model this relationship. In these cases, polynomial regression can be used.\nTo perform polynomial regression, you first need to choose the degree of the polynomial that you want to fit to the data. The degree of the polynomial determines the number of curvatures in the line. A polynomial of degree 1 is a straight line, while a polynomial of degree 2 has one curvature, and so on.\nOnce you have chosen the degree of the polynomial, you can then fit the model to the data by finding the coefficients of the polynomial that minimize the sum of the squared errors. The coefficients of the polynomial are then used to predict the value of y for a given value of x.\nOne of the main differences between linear and polynomial regression is the shape of the curve that is fit to the data. Linear regression fits a straight line to the data, while polynomial regression can fit curves of any degree. This makes polynomial regression more flexible and able to model more complex relationships between variables.\nAnother difference between linear and polynomial regression is the way that they handle outliers. Outliers are data points that are significantly different from the rest of the data. In linear regression, outliers can have a large influence on the slope of the line, which can result in an inaccurate model. However, in polynomial regression, the curve is able to bend and adapt to the outliers, which can result in a more accurate model.\nPolynomial regression can be a powerful tool for understanding and predicting complex relationships between variables. However, it is important to be careful when using polynomial regression, as it can be prone to overfitting. Overfitting occurs when the model is too complex and fits the noise in the data rather than the underlying trend. This can result in a model that is accurate for the training data, but performs poorly on new data. To avoid overfitting, it is important to choose the appropriate degree of the polynomial and to use cross-validation to assess the performance of the model.\nImplementation\n1from sklearn.model_selection import train_test_split 2from sklearn.preprocessing import PolynomialFeatures 3from sklearn.linear_model import LinearRegression 4 5 6# Split the data into training and test sets 7X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 8 9# Scale the features (optional) 10from sklearn.preprocessing import StandardScaler 11scaler = StandardScaler() 12X_train = scaler.fit_transform(X_train) 13X_test = scaler.transform(X_test) 14 15# use the PolynomialFeatures class to transform the independent variables into polynomial features 16poly = PolynomialFeatures(degree=2) 17X_train_poly = poly.fit_transform(X_train) 18X_test_poly = poly.transform(X_test) 19 20# Create the linear regression model 21model = LinearRegression() 22 23# Fit the model to the training data 24model.fit(X_train_poly, y_train) 25 26# Make predictions on the test data 27y_pred = model.predict(X_test_poly) Logistic Regression Logistic regression is a statistical method used for predicting the probability of a binary outcome. It is a type of regression analysis that is used when the dependent variable is dichotomous, or has only two possible values. Logistic regression is often used in fields such as marketing, finance, and psychology to predict the likelihood of an event occurring, such as whether a customer will purchase a product or whether a patient will respond to a treatment.\nLinear regression is a statistical method that is used to model the linear relationship between a dependent variable and one or more independent variables. It is used to predict a continuous outcome, such as the price of a house or the amount of rainfall in a given year.\nOne of the main differences between logistic regression and linear regression is the type of dependent variable that they are used to model. Logistic regression is used to model dichotomous variables, while linear regression is used to model continuous variables.\nAnother difference between logistic regression and linear regression is the way that the models are fit to the data. In linear regression, the model is fit by minimizing the sum of the squared errors between the predicted values and the actual values. In logistic regression, the model is fit by maximizing the likelihood of the observed data, given the model. This is done using an optimization algorithm, such as gradient descent.\nTo perform logistic regression, you first need to choose the independent variables that you want to include in the model. These variables should be chosen based on their relevance to the outcome that you are trying to predict. The logistic regression model is then fit to the data by estimating the coefficients of the independent variables. The coefficients represent the effect of each independent variable on the probability of the outcome occurring.\nOnce the model has been fit to the data, it can be used to make predictions about the probability of the outcome occurring for a given set of values of the independent variables. The predicted probability can be transformed into a binary prediction by setting a threshold value. For example, if the threshold is set at 0.5, then any predicted probability greater than 0.5 is classified as a positive outcome, while any predicted probability less than 0.5 is classified as a negative outcome.\nWhen not use Logistic regression\nThere are several situations when logistic regression may not be the best choice for analyzing data:\nWhen the dependent variable is not binary: Logistic regression is only suitable for predicting the probability of a binary outcome, such as whether an event will occur or not. If the dependent variable has more than two categories, logistic regression is not appropriate. When the independent variables are not independent: Logistic regression assumes that the independent variables are independent of each other. If there are strong correlations between the independent variables, the model may be biased and produce inaccurate results. When the data is imbalanced: Logistic regression is sensitive to class imbalances, where one class is much more prevalent than the other. This can lead to poor performance of the model on the minority class. When the data has non-linear relationships: Logistic regression is a linear model, so it may not be suitable for data that has non-linear relationships. In these cases, a non-linear model such as decision trees or support vector machines may be more appropriate. When the sample size is small: Logistic regression is prone to overfitting when the sample size is small. This means that the model may perform well on the training data, but poorly on new data. To avoid overfitting, it is important to have a large enough sample size to accurately estimate the coefficients of the model. Implementation\n1from sklearn.linear_model import LogisticRegression 2from sklearn.model_selection import train_test_split 3import pandas as pd 4from sklearn.metrics import accuracy_score, precision_score, recall_score 5 6 7# Load the data into a Pandas DataFrame 8df = pd.read_csv(\u0026#34;data.csv\u0026#34;) 9 10# Split the data into features and target variables 11X = df.drop(\u0026#34;target\u0026#34;, axis=1) 12y = df[\u0026#34;target\u0026#34;] 13 14# Split the data into training and testing sets 15X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) 16 17# Create an instance of the LogisticRegression class 18model = LogisticRegression() 19 20# Fit the model to the training data 21model.fit(X_train, y_train) 22 23# Make predictions on the testing data 24y_pred = model.predict(X_test) 25 26# Calculate the accuracy of the model 27accuracy = accuracy_score(y_test, y_pred) 28 29# Calculate the precision of the model 30precision = precision_score(y_test, y_pred) 31 32# Calculate the recall of the model 33recall = recall_score(y_test, y_pred) 34 35print(\u0026#34;Accuracy:\u0026#34;, accuracy) 36print(\u0026#34;Precision:\u0026#34;, precision) 37print(\u0026#34;Recall:\u0026#34;, recall) Support Vector Machines (SVM) Support Vector Machines (SVMs) are a type of supervised learning algorithm that can be used for classification or regression tasks. They are based on the idea of finding a hyperplane in a high-dimensional space that maximally separates different classes.\nIn the context of classification, an SVM model takes a set of labeled training data and tries to find the hyperplane that best separates the classes. This hyperplane is known as the \u0026quot;decision boundary.\u0026quot; Once the decision boundary has been determined, the model can be used to predict the class of new data points by determining which side of the hyperplane they fall on.\nOne key difference between SVMs and logistic regression is the way in which they model the relationship between the independent and dependent variables. Logistic regression models this relationship using a logistic function, which is a sigmoid curve that maps the predicted probability of a data point belonging to a particular class to the range [0, 1]. SVMs, on the other hand, do not model the probability of a data point belonging to a particular class; instead, they simply predict the class that a data point belongs to based on its position relative to the decision boundary.\nAnother difference is that logistic regression is a relatively simple algorithm, while SVMs can be more complex, depending on the kernel function and hyperparameters used. This can make SVMs more powerful, but also more prone to overfitting if the model is not properly regularized.\nHow to use SVM for classification problems\nAt first, you will need to prepare your data. This typically involves splitting your data into training and test sets, and possibly scaling or normalizing the features.\n1from sklearn.model_selection import train_test_split 2 3# Split the data into training and test sets 4X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 5 6# Scale the features (optional) 7from sklearn.preprocessing import StandardScaler 8scaler = StandardScaler() 9X_train = scaler.fit_transform(X_train) 10X_test = scaler.transform(X_test) Here, X is a 2D array containing the independent variables, and y is a 1D array containing the dependent variable. The test_size parameter specifies the proportion of the data that should be used for testing, and the random_state parameter ensures that the same split is obtained each time the code is run.\nNow, you can create an SVC object from the sklearn.svm module and fit it to the training data:\n1from sklearn.svm import SVC 2 3# Create the SVC model 4model = SVC(kernel=\u0026#39;rbf\u0026#39;) 5 6# Fit the model to the training data 7model.fit(X_train, y_train) Here, the kernel parameter specifies the kernel function to use. You can choose from a variety of kernel functions, such as the linear kernel, polynomial kernel, and RBF kernel.\nAfter the model has been trained, you can use it to make predictions on the test data:\n1# Make predictions on the test data 2y_pred = model.predict(X_test) Finally, you can evaluate the model's performance using various evaluation metrics. For example:\n1from sklearn.metrics import accuracy_score, precision_score, recall_score 2 3# Calculate the accuracy 4accuracy = accuracy_score(y_test, y_pred) 5 6# Calculate the precision 7precision = precision_score(y_test, y_pred) 8 9# Calculate the recall 10recall = recall_score(y_test, y_pred) The accuracy_score function calculates the proportion of predictions that are correct, the precision_score function calculates the proportion of true positive predictions among all positive predictions, and the recall_score function calculates the proportion of true positive predictions among all actual positive instances.\nHow to use SVM for regression problems\nSupport Vector Machines (SVMs) can be used for regression tasks by using a different loss function and prediction function than those used for classification tasks.\nFirst, you will need to install the sklearn module. You can do this by running the following command:\n1pip install sklearn Next, you will need to prepare your data. This typically involves splitting your data into training and test sets, and possibly scaling or normalizing the features. For example:\n1from sklearn.model_selection import train_test_split 2 3# Split the data into training and test sets 4X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 5 6# Scale the features (optional) 7from sklearn.preprocessing import StandardScaler 8scaler = StandardScaler() 9X_train = scaler.fit_transform(X_train) 10X_test = scaler.transform(X_test) Here, X is a 2D array containing the independent variables, and y is a 1D array containing the dependent variable. The test_size parameter specifies the proportion of the data that should be used for testing, and the random_state parameter ensures that the same split is obtained each time the code is run.\nNow, you can create an SVR object from the sklearn.svm module and fit it to the training data:\n1from sklearn.svm import SVR 2 3# Create the SVR model 4model = SVR(kernel=\u0026#39;rbf\u0026#39;) 5 6# Fit the model to the training data 7model.fit(X_train, y_train) Here, the kernel parameter specifies the kernel function to use. You can choose from a variety of kernel functions, such as the linear kernel, polynomial kernel, and RBF kernel.\nAfter the model has been trained, you can use it to make predictions on the test data:\n1# Make predictions on the test data 2y_pred = model.predict(X_test) Finally, you can evaluate the model's performance using various evaluation metrics. For example:\n1from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score 2 3# Calculate the mean absolute error (MAE) 4mae = mean_absolute_error(y_test, y_pred) 5 6# Calculate the mean squared error (MSE) 7mse = mean_squared_error(y_test, y_pred) 8 9# Calculate the root mean squared error (RMSE) 10rmse = np.sqrt(mse) 11 12# Calculate the R2 score 13r2 = r2_score(y_test, y_pred) Decision Trees Decision tree algorithms are a popular choice for data classification and regression tasks. They are simple to understand and interpret, and can be implemented in a variety of programming languages. In this blog, we will explain the basic concepts behind decision trees and walk through an example of how they work.\nAt a high level, decision tree algorithms work by creating a tree-like model of decisions based on certain features. Each internal node in the tree represents a \u0026quot;test\u0026quot; on an attribute, and each leaf node represents a class label. The algorithm starts at the root node and works its way down the tree, making decisions based on the test results at each node, until it reaches a leaf node and makes a final prediction.\nTo build a decision tree, the algorithm needs to determine which attributes are the most important for making the decision. It does this by calculating the \u0026quot;information gain\u0026quot; of each attribute, which is a measure of how much the attribute reduces the uncertainty of the final prediction. The attribute with the highest information gain is chosen as the root node, and the process is repeated on the remaining attributes until all the attributes have been used or a stopping criterion is reached.\nLet's walk through an example to better understand how decision trees work. Imagine we have a dataset of animals, and we want to classify them as either \u0026quot;mammals\u0026quot; or \u0026quot;reptiles\u0026quot;. The dataset contains three attributes: \u0026quot;has fur\u0026quot;, \u0026quot;lays eggs\u0026quot;, and \u0026quot;has scales\u0026quot;. We can use a decision tree to classify the animals based on these attributes.\nFirst, we need to determine which attribute is the most important for making the decision. In this case, the attribute \u0026quot;has fur\u0026quot; has the highest information gain, so it becomes the root node of the decision tree.\nNext, we split the dataset into two groups based on the value of the \u0026quot;has fur\u0026quot; attribute. If the animal has fur, it is classified as a mammal and is placed in the left branch of the tree. If the animal does not have fur, it is classified as a reptile and is placed in the right branch of the tree.\nAt this point, we have reduced the uncertainty of the final prediction by 50%. However, there may still be some animals that cannot be accurately classified based on the \u0026quot;has fur\u0026quot; attribute alone. For example, reptiles like snakes and lizards may not have fur, but they do lay eggs and have scales.\nTo further split the dataset, we need to determine which attribute is the most important for making the decision. In this case, the attribute \u0026quot;lays eggs\u0026quot; has the highest information gain, so it becomes the root node of the right branch of the tree.\nWe split the dataset into two groups based on the value of the \u0026quot;lays eggs\u0026quot; attribute. If the animal lays eggs, it is classified as a reptile and is placed in the left branch of the tree. If the animal does not lay eggs, it is classified as a mammal and is placed in the right branch of the tree.\nFinally, we reach a leaf node and make a final prediction. In this case, the animal is either a mammal or a reptile, depending on the values of the \u0026quot;has fur\u0026quot; and \u0026quot;lays eggs\u0026quot; attributes.\nDecision trees are a powerful and intuitive tool for classification and regression tasks. They are easy to understand and interpret, and can handle both continuous and categorical data. However, they can also be prone to overfitting, especially if the tree is allowed to grow too deep. To prevent overfitting, it is important to carefully tune the parameters of the decision tree and prune the tree as needed.\nWhen training a decision tree model on a given dataset, it is possible to improve the accuracy of the model by adding more and more splits to the tree. However, it is important to be mindful of overfitting, which is when the model becomes too complex and begins to fit the noise in the data rather than the underlying trend.\nOne way to mitigate the risk of overfitting is to use cross-validation on the training dataset. Cross-validation involves dividing the dataset into multiple subsets, training the model on one subset and evaluating it on the others. This allows us to get a better estimate of the model's generalization performance and helps us identify when we have reached the optimal number of splits for the decision tree.\nOne of the main advantages of decision tree models is their interpretability. When using a decision tree, it is easy to see which variables and values were used to split the data and make the final prediction. This can be useful for understanding the underlying decision-making process and identifying any potential biases in the model.\nImplementation\nImport the necessary libraries:\n1from sklearn import tree 2from sklearn.model_selection import train_test_split 3import pandas as pd Load the data into a Pandas dataframe and split it into features (X) and the target variable (y):\n1# Load the data into a Pandas dataframe 2df = pd.read_csv(\u0026#34;data.csv\u0026#34;) 3 4# Split the data into features and the target variable 5X = df.drop(\u0026#34;target\u0026#34;, axis=1) 6y = df[\u0026#34;target\u0026#34;] Split the data into a training set and a test set:\n1# Split the data into a training set and a test set 2X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) Create the decision tree model and fit it to the training data:\n1# Create the decision tree model 2model = tree.DecisionTreeClassifier() 3 4# Fit the model to the training data 5model.fit(X_train, y_train) Make predictions on the test set and evaluate the model:\n1# Make predictions on the test set 2predictions = model.predict(X_test) 3 4# Evaluate the model 5accuracy = model.score(X_test, y_test) 6print(\u0026#34;Accuracy:\u0026#34;, accuracy) This is a basic example of how to implement a decision tree model using scikit-learn in Python. There are many other parameters and options that can be fine-tuned to optimize the performance of the model, such as the maximum depth of the tree, the minimum number of samples required to split a node, and the criterion used to measure the quality of a split.\nImportant parameters of a decision tree max_depth: It can also be described as the length of the longest path from the tree root to a leaf. The root node is considered to have a depth of 0. The Max Depth value cannot exceed 30 on a 32-bit machine. The default value is 30.\nThe maximum depth that you allow the tree to grow to. The deeper you allow, the more complex your model will become. For training error, it is easy to see what will happen. If you increase max_depth, training error will always go down (or at least not go up).\nFor testing error, it gets less obvious. If you set max_depth too high, then the decision tree might simply overfit the training data without capturing useful patterns as we would like; this will cause testing error to increase. But if you set it too low, that is not good as well; then you might be giving the decision tree too little flexibility to capture the patterns and interactions in the training data. This will also cause the testing error to increase.\nThere is a nice golden spot in between the extremes of too-high and too-low. Usually, the modeller would consider the max_depth as a hyper-parameter, and use some sort of grid/random search with cross-validation to find a good number for max_depth .\nmin_samples_split: This parameter controls the minimum number of samples required to split a node. A larger value will result in a simpler model, but may lead to underfitting. A smaller value will allow the tree to capture more complex relationships in the data, but may lead to overfitting.\nmax_features: This parameter controls the maximum number of features that are used to split at each node. A smaller value will result in a simpler model, but may lead to underfitting. A larger value will allow the tree to capture more complex relationships in the data, but may lead to overfitting.\ncriterion: The criterion parameter in a decision tree model controls the function used to measure the quality of a split. In other words, it determines how the decision tree algorithm decides which attributes to split on at each node. There are two common choices for the criterion parameter: \u0026quot;gini\u0026quot; and \u0026quot;entropy\u0026quot;.\nThe Gini criterion measures the purity of the nodes in the tree. It is calculated as the sum of the square of the probability of each class in the node, with a value of 0 indicating complete purity (i.e., all the samples in the node belong to the same class) and a value of 1 indicating complete impurity (i.e., the samples in the node are equally distributed among all classes).\nThe entropy criterion measures the impurity of the nodes in the tree. It is calculated as the sum of the probability of each class in the node multiplied by the logarithm of the probability, with a value of 0 indicating complete purity and a larger value indicating more impurity.\nIn general, the choice of criterion will depend on the nature of the data and the desired properties of the model. The Gini criterion is typically faster to compute and is often used as the default criterion in decision tree algorithms. The entropy criterion is more computationally expensive, but may produce more balanced trees.\nRandom Forests Random forest is an ensemble machine learning algorithm that combines the predictions of multiple decision trees to improve the overall performance of the model. It is a powerful and widely-used tool for classification and regression tasks, and is known for its ability to handle large and complex datasets.\nAt a high level, random forest algorithms work by building multiple decision trees using a random subset of the data and features, and then averaging the predictions of the individual trees to make the final prediction. This process is repeated multiple times, and the resulting collection of trees is called a \u0026quot;forest\u0026quot;. The randomness in the selection of the data and features helps to reduce the risk of overfitting and improve the generalization performance of the model.\nHere is a more detailed description of the steps involved in building a random forest model:\nDraw a random sample of data points with replacement from the training dataset (this is known as bootstrapping). For each tree in the forest, draw a random sample of features with replacement (this is known as feature bagging). Build a decision tree using the bootstrapped data and the feature-bagged features. Repeat steps 1-3 multiple times to build a collection of decision trees. For a given input, make a prediction using each of the decision trees in the forest and average the predictions to obtain the final prediction. Random forest algorithms have several advantages over single decision trees, including improved accuracy, better generalization performance, and the ability to handle high-dimensional and correlated features. They are also resistant to overfitting and can be used for both classification and regression tasks.\nOne of the main disadvantages of random forest algorithms is their computational complexity. Building and training a random forest model can be time-consuming, especially for large datasets. In addition, random forests can be difficult to interpret, as the individual decision trees in the forest are typically not easily visualized.\nA random forest is like a black box and works as mentioned in above answer. It’s a forest you can build and control. You can specify the number of trees you want in your forest(n_estimators) and also you can specify max num of features to be used in each tree. But you cannot control the randomness, you cannot control which feature is part of which tree in the forest, you cannot control which data point is part of which tree. Accuracy keeps increasing as you increase the number of trees, but becomes constant at certain point. Unlike decision tree, it won’t create highly biased model and reduces the variance.\nWhen to use to decision tree:\nWhen you want your model to be simple and explainable When you want non parametric model When you don’t want to worry about feature selection or regularization or worry about multi-collinearity. You can overfit the tree and build a model if you are sure of validation or test data set is going to be subset of training data set or almost overlapping instead of unexpected. When to use random forest :\nWhen you don’t bother much about interpreting the model but want better accuracy. Random forest will reduce variance part of error rather than bias part, so on a given training data set decision tree may be more accurate than a random forest. But on an unexpected validation data set, Random forest always wins in terms of accuracy. Implementation\nImport the necessary libraries:\n1from sklearn.ensemble import RandomForestClassifier 2from sklearn.model_selection import train_test_split 3import pandas as pd Load the data into a Pandas dataframe and split it into features (X) and the target variable (y):\n1# Load the data into a Pandas dataframe 2df = pd.read_csv(\u0026#34;data.csv\u0026#34;) 3 4# Split the data into features and the target variable 5X = df.drop(\u0026#34;target\u0026#34;, axis=1) 6y = df[\u0026#34;target\u0026#34;] Split the data into a training set and a test set:\n1# Split the data into a training set and a test set 2X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) Create the random forest model and fit it to the training data:\n1# Create the random forest model 2model = RandomForestClassifier(n_estimators=100) 3 4# Fit the model to the training data 5model.fit(X_train, y_train) Make predictions on the test set and evaluate the model:\n1# Make predictions on the test set 2predictions = model.predict(X_test) 3 4# Evaluate the model 5accuracy = model.score(X_test, y_test) 6print(\u0026#34;Accuracy:\u0026#34;, accuracy) This is a basic example of how to implement a random forest model using scikit-learn in Python. There are many other parameters and options that can be fine-tuned to optimize the performance of the model, such as the number of trees in the forest (n_estimators), the maximum depth of the trees (max_depth), and the criterion used to measure the quality of a split (criterion).\nImportant parameters in Random Forest\nn_estimators: This parameter controls the number of decision trees in the random forest. A larger value will result in a more complex and potentially more accurate model, but may also increase the risk of overfitting. A smaller value will result in a simpler and potentially less accurate model, but may also reduce the risk of overfitting. max_depth: This parameter controls the maximum depth of the trees in the random forest. A smaller value will result in a simpler and more interpretable model, but may lead to underfitting. A larger value will allow the trees to capture more complex relationships in the data, but may lead to overfitting. Gradient Boosting Gradient boosting is a machine learning algorithm that combines the predictions of multiple weak models to create a strong ensemble model. It is a powerful and widely-used tool for classification and regression tasks, and is known for its ability to handle large and complex datasets.\nAt a high level, gradient boosting algorithms work by iteratively building a sequence of weak models, with each model trying to correct the mistakes of the previous model. The weak models are typically decision trees, but can also be other types of models, such as linear regression models. The final prediction is made by combining the predictions of all the individual models using a weighted sum.\nHere is a more detailed description of the steps involved in building a gradient boosting model:\nInitialize the model with a base prediction, such as the mean of the target variable in the training dataset. For each iteration, fit a weak model to the residuals (the difference between the true target variable and the current prediction) of the previous iteration. Update the prediction by adding the weighted prediction of the current iteration to the prediction of the previous iteration. Repeat steps 2 and 3 until a stopping criterion is reached, such as a maximum number of iterations or a minimum improvement in the loss function. Gradient boosting algorithms have several advantages over single decision trees, including improved accuracy, better generalization performance, and the ability.\nDifferences between Random Forest and Gradient Boosting\nPrediction process: In a random forest algorithm, each tree in the forest makes a prediction independently, and the final prediction is made by averaging the predictions of the individual trees. In a gradient boosting algorithm, each tree in the ensemble makes a prediction based on the errors of the previous trees, and the final prediction is made by adding the weighted predictions of the individual trees.\nComputational complexity: Random forest algorithms are generally faster to train and predict compared to gradient boosting algorithms, especially for large datasets. This is because gradient boosting algorithms involve fitting a weak model to the residuals of the previous iteration at each step, which can be computationally expensive.\nInterpretability: Random forest algorithms are generally more interpretable compared to gradient boosting algorithms, as the individual decision trees in the forest can be visualized and the importance of each feature can be easily computed. In contrast, gradient boosting algorithms involve a sequential process of fitting weak models to residuals, which can make the individual models difficult to interpret.\nOverfitting: Both random forest and gradient boosting algorithms can suffer from overfitting if the model is not properly regularized. However, gradient boosting algorithms are generally more prone to overfitting compared to random forest algorithms, especially if the learning rate is set too high or the number of iterations is too large.\nImplementation using XGBoost\n1import xgboost as xgb 2from sklearn.model_selection import train_test_split, GridSearchCV 3 4# Load the data 5X = # features 6y = # targets 7 8# Split the data into training and test sets 9X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) 10 11# Create the XGBoost model 12model = xgb.XGBClassifier() 13 14# Use cross-validation to tune the hyperparameters 15parameters = {\u0026#39;max_depth\u0026#39;: [3, 5, 7], \u0026#39;learning_rate\u0026#39;: [0.1, 0.3, 0.5]} 16clf = GridSearchCV(model, parameters, cv=5) 17 18# Fit the model using the training data 19clf.fit(X_train, y_train) 20 21# Evaluate the model on the test set 22score = clf.score(X_test, y_test) 23 24print(\u0026#34;Test score: {:.2f}\u0026#34;.format(score)) Principal Component Analysis Principal Component Analysis (PCA) is a statistical technique that is used to identify patterns in data and to reduce the dimensionality of large datasets. It is a widely used method for analyzing and visualizing high-dimensional data, and it has numerous applications in fields such as machine learning, computer vision, and data mining. In this blog, we will explore the underlying concepts and mechanics of PCA, and we will discuss how it can be used to extract important information from datasets.\nWhat is PCA? At its core, PCA is a method for identifying patterns in data. It is used to extract the most important features or dimensions from a dataset, and to represent the data in a reduced form. This process is known as dimensionality reduction, and it can be useful in a number of contexts.\nFor example, consider a dataset that contains information about a group of people, such as their age, height, weight, and gender. These data points can be represented as a four-dimensional dataset, with one dimension for each of the four variables. However, it may be difficult to visualize or analyze the data in this form, especially if the dataset is large.\nPCA can be used to reduce the dimensionality of this dataset by identifying the most important features, or dimensions, and representing the data in a lower-dimensional space. In this case, the algorithm might identify age and height as the two most important features, and it would create a new two-dimensional dataset based on these variables. This reduced dataset would be easier to visualize and analyze, and it would capture the most important patterns in the data.\nMechanism\nTo understand how PCA works, let's consider an example. Imagine that we have a data set containing information about the height and weight of a group of people. If we plot the data on a scatterplot, we might see a pattern emerge, with the points forming a line or curve. This pattern represents the relationship between the two variables, and is known as the principal component.\nTo find the principal component, we first standardize the data by subtracting the mean and dividing by the standard deviation. This ensures that all of the variables are on the same scale, which is important for PCA. Next, we calculate the covariance matrix, which tells us how the variables are related to one another. Finally, we find the eigenvectors and eigenvalues of the covariance matrix, which are used to determine the principal components.\nThe eigenvectors of the covariance matrix are the directions in which the data vary the most, and the eigenvalues tell us how much of the variance is captured by each eigenvector. The eigenvector with the highest eigenvalue is the first principal component, the eigenvector with the second highest eigenvalue is the second principal component, and so on.\nWe can then use these principal components to transform the original data into a new set of variables, which are linear combinations of the original variables. These new variables are called the principal component scores, and they capture as much of the variance in the data as possible.\nImplementation\nHere is an example of how to implement PCA in Python using the sklearn library:\n1from sklearn.decomposition import PCA 2 3# Load the data 4X = ... # your data, an n x m matrix where n is the number of samples and m is the number of features 5 6# Create the PCA model 7pca = PCA() 8 9# Fit the model to the data 10pca.fit(X) 11 12# Transform the data using the model 13X_transformed = pca.transform(X) 14 15# The transformed data has been reduced to the number of principal components specified in the model By default, the PCA model will keep all of the principal components, but you can specify the number of components you want to keep using the n_components parameter. For example, to keep only the top 2 principal components:\n1pca = PCA(n_components=2) You can also specify the fraction of variance you want to retain using the explained_variance_ratio_ attribute:\n1# Keep enough components to retain 95% of the variance 2pca = PCA(n_components=0.95) Finally, you can access the principal components themselves using the components_ attribute:\n1print(pca.components_) It's worth noting that PCA is sensitive to the scaling of the original features, so it's important to standardize the data before applying the algorithm. Additionally, PCA is a linear transformation technique, so it's not suitable for datasets with non-linear relationships. There are various non-linear dimensionality reduction techniques that can be used in these cases, such as t-SNE or UMAP.\nNaive Bayes Naive Bayes is a popular machine learning algorithm that is often used for classification tasks. It is based on the idea of using Bayes' theorem, which is a mathematical formula that calculates the probability of an event based on certain conditions.\nThe basic premise of Naive Bayes is that it assumes that all of the features in a dataset are independent of each other. This assumption is called the \u0026quot;naive\u0026quot; part of the algorithm because it is often not realistic in real-world data. However, despite this assumption, the algorithm has been shown to be very effective in many applications.\nTo understand how the Naive Bayes algorithm works, let's consider a simple example. Suppose we have a dataset with two features: \u0026quot;outlook\u0026quot; and \u0026quot;temperature\u0026quot;. Outlook can have three possible values: \u0026quot;sunny\u0026quot;, \u0026quot;cloudy\u0026quot;, and \u0026quot;rainy\u0026quot;. Temperature can have two possible values: \u0026quot;hot\u0026quot; and \u0026quot;cold\u0026quot;. We also have a target variable that can have two possible values: \u0026quot;yes\u0026quot; and \u0026quot;no\u0026quot;. The goal of the Naive Bayes algorithm is to predict the value of the target variable based on the values of the other two features.\nTo do this, we first need to calculate the probability of each of the possible values of the target variable. This is done using the following formula:\n1P(yes) = Number of instances where the target variable is \u0026#34;yes\u0026#34; / Total number of instances Similarly, we can calculate the probability of the target variable being \u0026quot;no\u0026quot;:\n1P(no) = Number of instances where the target variable is \u0026#34;no\u0026#34; / Total number of instances Next, we need to calculate the probability of each possible value of the features given the target variable. For example, we can calculate the probability of the outlook being \u0026quot;sunny\u0026quot; given that the target variable is \u0026quot;yes\u0026quot;:\n1P(sunny | yes) = Number of instances where the outlook is \u0026#34;sunny\u0026#34; and the target variable is \u0026#34;yes\u0026#34; / Total number of instances where the target variable is \u0026#34;yes\u0026#34; We can do this for all of the possible combinations of feature values and target variable values.\nOnce we have calculated all of these probabilities, we can use them to make predictions about the target variable. To do this, we use Bayes' theorem, which is written as:\n1P(A | B) = P(B | A) * P(A) / P(B) In the context of our example, A is the target variable and B is the combination of feature values that we are interested in. For example, if we want to predict the target variable given that the outlook is \u0026quot;sunny\u0026quot; and the temperature is \u0026quot;hot\u0026quot;, we would plug these values into the formula like this:\n1P(yes | sunny, hot) = P(sunny, hot | yes) * P(yes) / P(sunny, hot) We can then use the probabilities that we calculated earlier to fill in the rest of the formula.\nFinally, we can compare the probabilities of the target variable being \u0026quot;yes\u0026quot; and \u0026quot;no\u0026quot; and choose the one with the higher probability as our prediction.\nThere are many variations and refinements to the algorithm that can be used to improve its performance, but this is the basic idea behind it.\nImplementation\nHere is an example of how to use the sklearn library to train a Naive Bayes classifier in Python:\n1from sklearn.naive_bayes import GaussianNB 2from sklearn.model_selection import train_test_split 3 4# Load the dataset 5X = ... # feature values 6y = ... # target values 7 8# Split the data into training and test sets 9X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) 10 11# Create the classifier object 12clf = GaussianNB() 13 14# Train the classifier on the training data 15clf.fit(X_train, y_train) 16 17# Use the classifier to make predictions on the test data 18predictions = clf.predict(X_test) 19 20# Evaluate the classifier\u0026#39;s performance 21accuracy = clf.score(X_test, y_test) K-Nearest Neighbors (KNN) K-Nearest Neighbors (KNN) is a simple but powerful machine learning algorithm that is used for both classification and regression tasks. It works by finding the K closest data points in the training set to the input data point, and then using those data points to make a prediction.\nHere's how the KNN algorithm works in more detail:\nCollect the training data and the input data point. Calculate the distance between the input data point and each data point in the training set using a distance metric such as Euclidean distance. Find the K data points in the training set that are closest to the input data point. Use the K nearest neighbors to make a prediction. For classification tasks, the prediction is typically the most common class among the K nearest neighbors. For regression tasks, the prediction is typically the average of the K nearest neighbors.\nOne important parameter of the KNN algorithm is the value of K, which determines the number of nearest neighbors to use for the prediction. A larger value of K will result in a smoother decision boundary, but may also make the model more prone to bias. A smaller value of K will result in a more complex decision boundary, but may also make the model more sensitive to noise.\nDifferent approaches to choosing the value of K in KNN\nThe value of K in the K-Nearest Neighbors (KNN) algorithm is an important parameter that determines the complexity of the model and the sensitivity to noise. Choosing the right value of K is crucial for achieving good performance with the KNN algorithm.\nThere are a few different approaches to choosing the value of K in KNN:\nChoose K using a heuristic: One common heuristic is to choose K to be the square root of the number of data points in the training set. This heuristic works well in many cases, but may not always be the best choice. Use cross-validation: Another approach is to use cross-validation to choose the value of K. This involves dividing the training set into a number of folds, training the KNN model on each fold, and evaluating the performance on each fold. The value of K that yields the best performance across all folds is then chosen as the optimal value. Use a grid search: A third approach is to use a grid search to exhaustively search over a range of possible values of K and choose the value that yields the best performance. This can be time-consuming, but is a thorough and reliable way to choose the optimal value of K. It is generally a good idea to try a few different values of K and see how the model performs in each case. This will give you a sense of how sensitive the model is to the value of K and help you choose the best value for your particular dataset.\nHere is an example of how you might use cross-validation to choose the value of K in Python using the sklearn library:\n1from sklearn.neighbors import KNeighborsClassifier 2from sklearn.model_selection import cross_val_score 3 4# Load the dataset 5X = ... # feature values 6y = ... # target values 7 8# Create a list of possible values for K 9k_values = [1, 3, 5, 7, 9, 11] 10 11# Create an empty list to store the scores 12scores = [] 13 14# Loop over the possible values of K 15for k in k_values: 16 # Create the classifier object 17 clf = KNeighborsClassifier(n_neighbors=k) 18 19 # Use cross-validation to evaluate the classifier\u0026#39;s performance 20 score = cross_val_score(clf, X, y, cv=5).mean() 21 22 # Add the score to the list of scores 23 scores.append(score) 24 25# Find the value of K that yields the best performance 26best_k = k_values[np.argmax(scores)] This code will use cross-validation to evaluate the performance of the KNN model for a range of values of K. The value of K that yields the best performance is then chosen as the optimal value.\nK-means Clustering K-Means is a popular clustering algorithm that is used to partition a dataset into a given number of clusters. It works by iteratively assigning each data point to the nearest cluster and then updating the cluster centroids based on the data points assigned to it.\nHere's how the K-Means algorithm works in more detail:\nChoose the number of clusters K and initialize the centroids of the K clusters. This can be done randomly or using some other method such as k-means++. Assign each data point to the nearest cluster based on the distance to the centroid. Update the centroids of the K clusters by taking the mean of all of the data points assigned to that cluster. Repeat steps 2 and 3 until convergence, which occurs when the assignments of data points to clusters stop changing. The goal of the K-Means algorithm is to minimize the within-cluster sum of squares, which is the sum of the squared distances of the data points within each cluster to the centroid of that cluster. The algorithm iteratively updates the centroids to minimize this objective function.\nOne important parameter of the K-Means algorithm is the value of K, which determines the number of clusters to partition the data into. Choosing the right value of K is crucial for achieving good performance with the K-Means algorithm. The elbow method is a common heuristic for choosing the value of K in K-Means.\nHere is an example of how to implement the K-Means algorithm in Python using the sklearn library:\n1from sklearn.cluster import KMeans 2 3# Load the dataset 4X = ... # feature values 5 6# Create the KMeans object 7kmeans = KMeans(n_clusters=3) 8 9# Fit the KMeans object to the data 10kmeans.fit(X) 11 12# Get the cluster assignments 13cluster_assignments = kmeans.predict(X) 14 15# Get the cluster centroids 16cluster_centroids = kmeans.cluster_centers_ This code will create a KMeans object, fit it to the data, and then use it to predict the cluster assignments of the data points and retrieve the cluster centroids.\nElbow method for choosing the value of K\nThe elbow method is a heuristic used to choose the optimal number of clusters in a clustering algorithm. It works by fitting the clustering algorithm to the data for a range of values of the number of clusters, and then plotting the value of the objective function (such as the within-cluster sum of squares) as a function of the number of clusters. The idea is to choose the number of clusters at the \u0026quot;elbow\u0026quot; of the curve, which is the point where the rate of improvement begins to slow down.\nThe elbow method is often used in conjunction with the K-Means clustering algorithm, which is a popular method for partitioning a dataset into a given number of clusters. To use the elbow method with K-Means, you would fit the algorithm to the data for a range of values of K (the number of clusters), and then plot the within-cluster sum of squares as a function of K. The optimal value of K is then chosen as the value at the elbow of the curve.\nHere is an example of how to use the elbow method to choose the number of clusters in Python:\n1from sklearn.cluster import KMeans 2import matplotlib.pyplot as plt 3 4# Load the dataset 5X = ... # feature values 6 7# Create a list of possible values for K 8k_values = range(1, 10) 9 10# Create an empty list to store the within-cluster sum of squares 11wcss = [] 12 13# Loop over the possible values of K 14for k in k_values: 15 # Create the KMeans object 16 kmeans = KMeans(n_clusters=k) 17 18 # Fit the KMeans object to the data 19 kmeans.fit(X) 20 21 # Add the within-cluster sum of squares to the list 22 wcss.append(kmeans.inertia_) 23 24# Plot the within-cluster sum of squares as a function of K 25plt.plot(k_values, wcss) 26plt.xlabel(\u0026#39;Number of clusters\u0026#39;) 27plt.ylabel(\u0026#39;Within-cluster sum of squares\u0026#39;) 28plt.show() This code will fit the K-Means clustering algorithm to the data for a range of values of K and plot the within-cluster sum of squares as a function of K. You can then visually inspect the plot to determine the optimal value of K at the elbow of the curve.\nAuthor: Sadman Kabir Soumik\nReferences:\nHow Does Linear Regression Actually Work? An Intuitive Approach to Linear Regression Introduction to Linear Regression and Polynomial Regression ","link":"https://blog.sksoumik.com/artificial-intelligence/common-machine-learning-algorithms/","section":"artificial-intelligence","tags":["machine learning","data science","algorithms"],"title":"Understanding Top 10 Classical Machine Learning Algorithms"},{"body":" Author: Sadman Kabir Soumik\nOriginal Two Sum Problem Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order.\n1Example 1: 2Input: nums = [2,7,11,15], target = 9 3Output: [0,1] 4Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. 5 6Example 2: 7Input: nums = [3,2,4], target = 6 8Output: [1,2] 9 10Example 3: 11Input: nums = [3,3], target = 6 12Output: [0,1] Brute Force\n1class Solution: 2 def twoSum(self, nums: List[int], target: int) -\u0026gt; List[int]: 3 n = len(nums) 4 for i in range(n): 5 for j in range(i+1, n): 6 if nums[i] + nums[j] == target: 7 return [i, j] Time complexity: O(n^2); n is the length of the input array. Space complexity: O(1); as we are only using a few extra variables and not using any extra data structures to store intermediate results.\nOptimized\n1class Solution: 2 def twoSum(self, nums: List[int], target: int) -\u0026gt; List[int]: 3 4 num_map = {} # put numbers as key and index as values 5 # Enumerate through the list \u0026#39;nums\u0026#39;. \u0026#39;enumerate\u0026#39; gives us an 6 # index (idx) and the item at that index (num) 7\t# For example, if nums = [2, 7, 11, 15], 8 # after this loop, num_map = {2: 0, 7: 1, 11: 2, 15: 3} 9 for idx, num in enumerate(nums): 10 # key: value = number, index 11 num_map[num] = idx 12 13 for idx, num in enumerate(nums): 14 # Calculate the complement of the current number 15 # (i.e., the difference between \u0026#39;target\u0026#39; and this number). 16 com = target - num 17 # If the complement exists in the dictionary \u0026#39;num_map\u0026#39; 18 # and it\u0026#39;s not the current number... 19 if com in num_map and idx != num_map[com]: 20 return [idx, num_map[com]] Time Complexity: O(n); The code includes a single loop that iterates over the entirety of the input array 'arr' (where 'n' is the length of 'arr'). The operations within this loop - dictionary lookups and updates - each have a constant time complexity of O(1). Therefore, the overall time complexity remains linear, i.e., O(n).\nSpace Complexity: O(n); The code uses a dictionary to store the indices of all elements in the input array 'arr'. Since the size of this dictionary directly scales with the size of the input array 'arr', the space complexity is O(n).\nVariant 1: Data Structure Design This is a LeetCode premium problem.\nDesign and implement a TwoSum class. It should support the following operations: add and find. add - Add the number to an internal data structure. find - Find if there exists any pair of numbers which sum is equal to the value.\n1For example, 2add(1); add(3); add(5); 3find(4) -\u0026gt; true 4find(7) -\u0026gt; false 1class TwoSum: 2 def __init__(self): 3 self.numbers = [] 4 5 def add(self, number): 6 self.numbers.append(number) 7 8 def find(self, target): 9 seen = set() 10 for num in self.numbers: 11 complement = target - num 12 if complement in seen: 13 return True 14 seen.add(num) 15 return False Variant 2 - Two Sum II - Input Array Is Sorted Given a 1-indexed array of integers numbers that is already *sorted in non-decreasing order*, find two numbers such that they add up to a specific target number. Let these two numbers be numbers[index1] and numbers[index2] where 1 \u0026lt;= index1 \u0026lt; index2 \u0026lt; numbers.length. Return the indices of the two numbers, index1 and index2, added by one as an integer array [index1, index2] of length 2. The tests are generated such that there is exactly one solution. You may not use the same element twice. Your solution must use only constant extra space.\nExample 1:\n1Input: numbers = [2,7,11,15], target = 9 2Output: [1,2] 3Explanation: The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2. We return [1, 2]. Example 2:\n1Input: numbers = [2,3,4], target = 6 2Output: [1,3] 3Explanation: The sum of 2 and 4 is 6. Therefore index1 = 1, index2 = 3. We return [1, 3]. 1class Solution: 2 def twoSum(self, numbers: List[int], target: int) -\u0026gt; List[int]: 3 left, right = 0, len(numbers) - 1 4 while left \u0026lt; right: 5 current_sum = numbers[left] + numbers[right] 6 if current_sum \u0026gt; target: 7 right -= 1 8 elif current_sum \u0026lt; target: 9 left += 1 10 else: 11 return [left + 1, right + 1] Variant 3 - Two Sum Less Than K This is a LeetCode premium problem.\nGiven an array A of integers and integer K, return the maximum S such that there exists i \u0026lt; j with A[i] + A[j] = S and S \u0026lt; K. If no i, j exist satisfying this equation, return -1.\n1Example 1: 2Input: A = [34,23,1,24,75,33,54,8], K = 60 3Output: 58 4Explanation: 5We can use 34 and 24 to sum 58 which is less than 60. 6 7Example 2: 8Input: A = [10,20,30], K = 15 9Output: -1 10Explanation: 11In this case it\u0026#39;s not possible to get a pair sum less that 15. Brute Force\n1# comparing all pairs 2def max_sum_less_than_k(A, K): 3 n = len(A) 4 max_sum = -1 5 6 for i in range(n): 7 for j in range(i + 1, n): 8 current_sum = A[i] + A[j] 9 if current_sum \u0026lt; K and current_sum \u0026gt; max_sum: 10 max_sum = current_sum 11 12 return max_sum Time complexity: O(n^2) Space complexity: O(1)\nOptimized\n1# using two pointers 2def max_sum_less_than_k(A, K): 3 A.sort() # sort the array in ascending order 4 n = len(A) 5 max_sum = -1 6 left = 0 # pointer starting from the left 7 right = n - 1 # pointer starting from the right 8 9 while left \u0026lt; right: 10 current_sum = A[left] + A[right] 11 if current_sum \u0026lt; K: 12 # # update max_sum if necessary 13 max_sum = max(max_sum, current_sum) 14 # move the left pointer to the right 15 # so that we can make larger sum 16 left += 1 17 else: 18 # move the right pointer to the left 19 right -= 1 20 21 return max_sum Time complexity: O(n log n) due to the initial sorting step. Space complexity: O(1) Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/all-leetcode-two-sum-problem-variations-with-python-solutions/","section":"software-engineering","tags":["algorithms","leetcode","problem solving"],"title":"Cracking the LeetCode Two Sum Variations - All 5 Problems"},{"body":"There are 4 main approaches you can consider for model compression. They are:\nQuantization Pruning Knowledge Distillation Low-Rank Factorization Quantization Quantization is the most general and commonly used model compression method. Quantization reduces a model’s size by using fewer bits to represent its parameters. By default, most software packages use 32 bits to represent a float number (single precision floating point). If a model has 100M parameters and each requires 32 bits to store, it’ll take up 400 MB.\nIf we use 16 bits to represent a number, we’ll reduce the memory footprint by half. Using 16 bits to represent a float is called half precision. Instead of using floats, you can have a model entirely in integers; each integer takes only 8 bits to represent. This method is also known as “fixed point.”\nIn fixed-point quantization, model parameters and activations are represented using a fixed number of bits, rather than the full precision of floating-point numbers. This allows for a trade-off between model size and performance, as using fewer bits can reduce the model's size but may also degrade its accuracy.\nTo apply quantization to a machine learning model in TensorFlow, you can use the tf.quantization module. This module provides functions and classes for quantizing both model parameters and activations, as well as for managing the resulting quantized models.\nHere is an example of how you might use the tf.quantization module to quantize a simple TensorFlow model:\n1import tensorflow as tf 2 3# Build a simple model 4inputs = tf.keras.Input(shape=(784,)) 5x = tf.keras.layers.Dense(128, activation=\u0026#39;relu\u0026#39;)(inputs) 6x = tf.keras.layers.Dense(128, activation=\u0026#39;relu\u0026#39;)(x) 7outputs = tf.keras.layers.Dense(10)(x) 8model = tf.keras.Model(inputs=inputs, outputs=outputs) 9 10# Convert the model to a quantized version 11converter = tf.lite.TFLiteConverter.from_keras_model(model) 12converter.optimizations = [tf.lite.Optimize.DEFAULT] 13quantized_model = converter.convert() In this example, we use the TFLiteConverter class from the tf.lite module to convert the original model to a quantized version. We enable the default optimization settings, which includes quantization, and then use the convert method to generate the quantized model.\nOnce you have a quantized model, you can use it just like any other TensorFlow model, by loading it and using it for inference or further training.\nPruning Pruning was a method originally used for decision trees where you remove sections of a tree that are uncritical and redundant for classification. As neural networks gained wider adoption, people started to realize that neural networks are over-parameterized and began to find ways to reduce the workload caused by the extra parameters.\nThere are several different ways to perform pruning, but one common approach is called \u0026quot;weight pruning.\u0026quot; In weight pruning, the goal is to remove as many connections (i.e., weights) from the model as possible, while maintaining a certain level of performance. This is typically done by first training the model to convergence, and then pruning a certain percentage of the lowest-magnitude weights in each layer. The pruned weights are then set to zero, effectively removing them from the model.\nAnother approach to pruning is called \u0026quot;structured pruning,\u0026quot; which involves removing entire units (e.g., neurons) or groups of units from the model. This can be done using a similar approach to weight pruning, where the lowest-performing units are identified and removed.\nTo apply pruning to a machine learning model in TensorFlow, you can use the tf.contrib.model_pruning module. This module provides functions and classes for pruning model parameters and for managing the resulting pruned models.\nHere is an example of how you might use the tf.contrib.model_pruning module to prune a simple TensorFlow model:\n1import tensorflow as tf 2 3# Build a simple model 4inputs = tf.keras.Input(shape=(784,)) 5x = tf.keras.layers.Dense(128, activation=\u0026#39;relu\u0026#39;)(inputs) 6x = tf.keras.layers.Dense(128, activation=\u0026#39;relu\u0026#39;)(x) 7outputs = tf.keras.layers.Dense(10)(x) 8model = tf.keras.Model(inputs=inputs, outputs=outputs) 9 10# Apply pruning to the model 11pruning_params = { 12 \u0026#39;pruning_schedule\u0026#39;: tf.contrib.model_pruning.PolynomialDecay( 13 initial_sparsity=0.50, 14 final_sparsity=0.90, 15 begin_step=2000, 16 end_step=4000) 17} 18pruned_model = tf.contrib.model_pruning.prune_low_magnitude(model, **pruning_params) In this example, we use the prune_low_magnitude function from the tf.contrib.model_pruning module to prune the weights in the model. We specify a pruning schedule using the PolynomialDecay class, which defines how the sparsity (i.e., the percentage of weights to be pruned) will change over time. In this case, we start with a sparsity of 50% and increase it to 90% over the course of 2000 to 4000 training steps.\nOnce you have a pruned model, you can use it just like any other TensorFlow model, by loading it and using it for inference or further training. You may also need to fine-tune the pruned model to restore its performance to the desired level.\nKnowledge Distillation Knowledge distillation is a technique for compressing machine learning models by training a smaller model to mimic the behavior of a larger, pre-trained model. The smaller model, also called the student model, is trained to reproduce the outputs of the larger, pre-trained model, known as the teacher model, on a set of training data.\nIn knowledge distillation, the student model is typically trained using a combination of the true labels for the training data and the output probabilities produced by the teacher model. This allows the student model to learn not only from the true labels, but also from the knowledge encoded in the teacher model's predictions.\nOnce the student model is trained, it can be used in place of the teacher model, providing a more efficient and compact alternative. The student model may not perform as well as the teacher model on the training data, but it should be able to generalize to unseen data in a similar way.\nOne example of a distilled network used in production is DistilBERT, which reduces the size of a BERT model by 40% while retaining 97% of its language understanding capabilities and being 60% faster.\nLow-Rank Factorization Low-rank factorization is a technique for compressing machine learning models by approximating a large, dense matrix with a much smaller, low-rank matrix. This can be done using a variety of methods, such as singular value decomposition (SVD) or matrix factorization.\nIn general, a low-rank matrix can be represented as the product of two much smaller matrices, one representing the \u0026quot;weights\u0026quot; and the other representing the \u0026quot;activations\u0026quot; of the original matrix. By using a low-rank approximation, it is possible to reduce the number of parameters in the model significantly, while maintaining a similar level of performance.\nFor example, by using a number of strategies including replacing 3 × 3 convolution with 1 × 1 convolution, SqueezeNets achieves AlexNet-level accuracy on ImageNet with 50 times fewer parameters.\nAuthor: Sadman Kabir Soumik\nReferences: Book - Designing Machine Learning Systems by Chip Huyen\n","link":"https://blog.sksoumik.com/artificial-intelligence/machine-learning-model-compression-techniques/","section":"artificial-intelligence","tags":["mlops","optimization","machine learning","data science"],"title":"ML Model Compression Techniques - Reducing Size and Improving Performance"},{"body":"","link":"https://blog.sksoumik.com/tags/optimization/","section":"tags","tags":null,"title":"optimization"},{"body":"","link":"https://blog.sksoumik.com/tags/elasticsearch/","section":"tags","tags":null,"title":"elasticsearch"},{"body":"","link":"https://blog.sksoumik.com/tags/programming/","section":"tags","tags":null,"title":"programming"},{"body":"Elasticsearch Elasticsearch is a distributed, open-source search and analytics engine based on the Apache Lucene search library. It's designed to provide fast and scalable search and analysis capabilities for large volumes of data.\nAt its core, Elasticsearch is a document-oriented database that stores data in JSON format. This allows it to index and search through data quickly and efficiently. Elasticsearch uses a powerful query language called Elasticsearch Query DSL to perform complex search queries on this data.\nOne of the key features of Elasticsearch is its distributed architecture. This means that it can automatically distribute data and search requests across multiple servers, allowing it to scale horizontally and handle large amounts of data.\nElasticsearch also provides many useful features and capabilities out-of-the-box, including full-text search, faceted search, and real-time analytics. It can be easily integrated into existing applications and systems, making it a versatile and powerful tool for a wide range of use cases.\nInstalling Elasticsearch I am using Linux Ubuntu 20.04.5 LTS. So, let's first install Elasticsearch on my machine. The following instruction works for all 20+ Ubuntu Versions.\nSo, let's first\n1curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elastic.gpg Next, add the Elastic source list to the sources.list.d directory, where apt will search for new sources:\n1echo \u0026#34;deb [signed-by=/usr/share/keyrings/elastic.gpg] https://artifacts.elastic.co/packages/7.x/apt stable main\u0026#34; | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list The [signed-by=/usr/share/keyrings/elastic.gpg] portion of the file instructs apt to use the key that you downloaded to verify repository and file information for Elasticsearch packages.\nNext, update your package lists so APT will read the new Elastic source:\n1sudo apt update Then install Elasticsearch with this command:\n1sudo apt install elasticsearch Press Y when prompted to confirm installation. If you are prompted to restart any services, press ENTER to accept the defaults and continue. Elasticsearch is now installed and ready to be configured.\nConfiguring Elasticsearch To configure Elasticsearch, we will edit its main configuration file elasticsearch.yml where most of its configuration options are stored. This file is located in the /etc/elasticsearch directory.\nUse your preferred text editor to edit Elasticsearch’s configuration file. Here, I’ll use nano:\n1sudo nano /etc/elasticsearch/elasticsearch.yml The elasticsearch.yml file provides configuration options for your cluster, node, paths, memory, network, discovery, and gateway. Most of these options are preconfigured in the file but you can change them according to your needs. For the purposes of our demonstration of a single-server configuration, we will only adjust the settings for the network host.\nElasticsearch listens for traffic from everywhere on port 9200. You will want to restrict outside access to your Elasticsearch instance to prevent outsiders from reading your data or shutting down your Elasticsearch cluster through its [REST API].\nTo restrict access and therefore increase security, find the line that specifies network.host, uncomment it, and replace its value with localhost so it reads like this:\n/etc/elasticsearch/elasticsearch.yml\n1. . . 2# ---------------------------------- Network ----------------------------------- 3# 4# Set the bind address to a specific IP (IPv4 or IPv6): 5# 6network.host: localhost 7. . . We have specified localhost so that Elasticsearch listens on all interfaces and bound IPs. If you want it to listen only on a specific interface, you can specify its IP in place of localhost. Save and close elasticsearch.yml. If you’re using nano, you can do so by pressing CTRL+X, followed by Y and then ENTER .\nStart the Elasticsearch service with systemctl.\n1sudo systemctl start elasticsearch Next, run the following command to enable Elasticsearch to start up every time your server boots:\n1sudo systemctl enable elasticsearch Securing Elasticsearch By default, Elasticsearch can be controlled by anyone who can access the HTTP API. This is not always a security risk because Elasticsearch listens only on the loopback interface (that is, 127.0.0.1), which can only be accessed locally. Thus, no public access is possible and as long as all server users are trusted, security may not be a major concern.\nWe will now configure the firewall to allow access to the default Elasticsearch HTTP API port (TCP 9200) for the trusted remote host, generally the server you are using in a single-server setup, such as198.51.100.0. To allow access, type the following command:\n1sudo ufw allow from 198.51.100.0 to any port 9200 Once that is complete, you can enable UFW with the command:\n1sudo ufw enable Finally, check the status of UFW with the following command:\n1sudo ufw status If you have specified the rules correctly, you should receive output like this:\n1Output 2Status: active 3 4To Action From 5-- ------ ---- 69200 ALLOW 198.51.100.0 722 ALLOW Anywhere 822 (v6) ALLOW Anywhere (v6) Testing Elasticsearch By now, Elasticsearch should be running on port 9200. You can test it with cURL and a GET request.\n1curl -X GET \u0026#39;http://localhost:9200\u0026#39; You should receive the following response:\n1Output 2{ 3 \u0026#34;name\u0026#34; : \u0026#34;elastic-22\u0026#34;, 4 \u0026#34;cluster_name\u0026#34; : \u0026#34;elasticsearch\u0026#34;, 5 \u0026#34;cluster_uuid\u0026#34; : \u0026#34;DEKKt_95QL6HLaqS9OkPdQ\u0026#34;, 6 \u0026#34;version\u0026#34; : { 7 \u0026#34;number\u0026#34; : \u0026#34;7.17.1\u0026#34;, 8 \u0026#34;build_flavor\u0026#34; : \u0026#34;default\u0026#34;, 9 \u0026#34;build_type\u0026#34; : \u0026#34;deb\u0026#34;, 10 \u0026#34;build_hash\u0026#34; : \u0026#34;e5acb99f822233d62d6444ce45a4543dc1c8059a\u0026#34;, 11 \u0026#34;build_date\u0026#34; : \u0026#34;2022-02-23T22:20:54.153567231Z\u0026#34;, 12 \u0026#34;build_snapshot\u0026#34; : false, 13 \u0026#34;lucene_version\u0026#34; : \u0026#34;8.11.1\u0026#34;, 14 \u0026#34;minimum_wire_compatibility_version\u0026#34; : \u0026#34;6.8.0\u0026#34;, 15 \u0026#34;minimum_index_compatibility_version\u0026#34; : \u0026#34;6.0.0-beta1\u0026#34; 16 }, 17 \u0026#34;tagline\u0026#34; : \u0026#34;You Know, for Search\u0026#34; 18} If you receive a response similar to the one above, Elasticsearch is working properly.\nCreate Elasticsearch Connection and Index for DB To make a connection with your Elasticsearch DB, write the following from Your Python Script,\n1from elasticsearch import Elasticsearch 2 3 4es = Elasticsearch(HOST=\u0026#34;localhost\u0026#34;, PORT=9200) 5es = Elasticsearch() By default, Elasticsearch runs on PORT 9200, and we are running the cluster in our local machine.\nNote: The above code works for the elasticsearch==7.17.4 version. For other versions, there is a high chance, the above syntax is different.\nNow, let's prepare our CSV file, so that we can insert in into our Elasticsearch. I will use Pandas to make some changes in the CSV data.\n1import pandas as pd 2 3# read the CSV file from the disk. 4df = pd.read_csv(\u0026#34;../file_dir/filename.csv\u0026#34;) 5 6# print all the columns 7print(df.columns.tolist()) output\n1[\u0026#39;product_url\u0026#39;, 2 \u0026#39;product_title\u0026#39;, 3 \u0026#39;product_rating\u0026#39;, 4 \u0026#39;product_caption\u0026#39;, 5 \u0026#39;product_description\u0026#39;, 6 \u0026#39;reviews\u0026#39;, 7 \u0026#39;img_links\u0026#39;] I have the above columns in my dataset.\nNow, I will replace all null values with N/A.\n1df.fillna(value=\u0026#34;N/A\u0026#34;, inplace=True) Create Database Index for Elasticsearch Let's create a DB index with a name called db_name.\n1es.indices.create(index=\u0026#34;db_name\u0026#34;, ignore=500) Now, let's check if the index has been created.\n1es.indices.exists(index=\u0026#34;db_name\u0026#34;) If it has been created, it must show True.\nBulk Load Data into DB Index Now, let's insert our Pandas Dataframe df into the index.\n1from elasticsearch import helpers 2 3helpers.bulk(es, df.to_dict(orient=\u0026#34;records\u0026#34;), index=\u0026#34;db_name\u0026#34;, timeout=\u0026#34;300s\u0026#34;) df.to_dict(orient=\u0026quot;records\u0026quot;) will convert our dataframe into JSON format. Read more on helpers from here in the doc.\nThis should successfully insert all of our data into the db_name index.\nSearch on the Elasticsearch Index Let's search for something for our product_title\n1content_query = es.search( 2 index=\u0026#34;db_name\u0026#34;, 3 body={\u0026#34;query\u0026#34;: {\u0026#34;match\u0026#34;: {\u0026#34;product_title\u0026#34;: \u0026#34;Mechanical Keyboards\u0026#34;}}}, 4 timeout=\u0026#34;300s\u0026#34;, 5) 6 7print(content_query) This will display all the relevant content related to Mechanical Keyboards in JSON format.\nIf you want to display only particular content, then you can run something like the below:\n1for hit in content_query[\u0026#34;hits\u0026#34;][\u0026#34;hits\u0026#34;]: 2 print(hit[\u0026#34;_source\u0026#34;][\u0026#34;product_url\u0026#34;]) This will only print the product_url related to Mechanical Keyboards.\nThanks for the read.\nAuthor: Sadman Kabir Soumik\nReference:\nhttps://www.digitalocean.com/community/tutorials/how-to-install-and-configure-elasticsearch-on-ubuntu-22-04 https://elasticsearch-py.readthedocs.io/en/7.x/helpers.html ","link":"https://blog.sksoumik.com/software-engineering/elasticsearch-python-client-tutorial/","section":"software-engineering","tags":["project-tutorial","programming","python","elasticsearch"],"title":"Working with Elasticsearch on Linux Using Python Client"},{"body":"What is System Design in Software Engineering? System design in software engineering is defining the structure, components, connections, and information for a system that meets specific needs. It is a vital step in software development as it outlines how the system will work and be structured. A team of developers usually performs this process with input from business analysts and end-users.\nProcess of Designing a System To create a good software system, we need to follow these steps:\nIdentify and understand what the system needs to do, and what it can and cannot do. For example, performance, reliability, and security requirements. Figure out how the system will be structured, including what parts it will have and how they will interact. Design each part/component of the system, including what it does, how it stores data, and how it processes information. Make sure the system can handle various amounts of loads, is easy to maintain, and can be improved later if needed. Pick the right tools to build the system with. Write down exactly what the system needs to do, and use this as a guide while building the software. The point of all this is to make a software system that works well and does what everyone needs it to do.\nCore Concepts of System Design Client-Server Model The client-server model is a software architecture where a client is a computer or device that asks for something from a server, and a server is a computer or device that responds to the request. This model is often used in networked systems, where a client asks for something from a server over a network, and the server sends back the requested thing or service.\nOne of the main benefits of the client-server model is that it separates the parts of the system. The client handles what the user interacts with, and the server manages the data and how the system works. This makes it easier to make changes to the system, as updates to the client or server don't affect the other side. The client-server model's capacity to manage numerous clients concurrently is an additional advantage. This can be helpful in systems with lots of users, as the server can easily deal with the extra work.\nNetwork Protocols Network protocols are rules and standards that control how computers and devices communicate over a network. In system design, network protocols are important for enabling different components of a system to exchange information and coordinate their actions.\nThere are many types of network protocols, each designed for specific purposes and operating at different layers of the network stack. At the lowest layer, there are protocols like Ethernet and Wi-Fi, which define how devices physically connect to the network and exchange data. At the next layer, there are protocols like TCP and UDP, which provide reliable and efficient data transmission between devices. At the top layer, there are protocols like HTTP and FTP, which define the formats and rules for exchanging specific types of data, like web pages and files.\nChoosing the right network protocols is important for ensuring that a system can work correctly and efficiently. For example, if a system needs to transfer a lot of data in real-time, a protocol like UDP may be better than TCP, as UDP is faster but doesn't provide the same level of error checking and reliability.\nStorage In system design, storage refers to how data is saved and accessed. This includes temporary storage, like your computer's memory (RAM), and permanent storage, like hard drives and databases. Storage is critical in a system because it enables data to be saved and retrieved. This data can be user-generated content, such as text, images, and videos, or system-generated data, like logs and records.\nWhen designing a system's storage, several key factors must be considered. First, the system must have enough storage capacity to handle its expected data. This may involve using different storage devices, like hard drives and cloud storage.\nSecond, the system must have efficient algorithms and data structures for storing and accessing data. This includes choosing the right data formats and structures, like tables and indexes, to optimize data retrieval and update performance.\nThird, the system must have mechanisms for protecting and backing up data to prevent data loss and ensure data integrity. This may involve redundant storage systems, backup procedures, and error-correction algorithms.\nLatency and Throughput When designing a system, we need to make sure it can handle its workload well. To do this, we look at two important metrics: latency and throughput. Latency refers to the time it takes for a system to respond to a request, while throughput refers to the amount of data that the system can process within a given period of time. If a system has high latency, users may get frustrated waiting for the system to respond. If it has low throughput, it may not be able to handle a lot of requests.\nTo improve these metrics, we can take a few steps. We can optimize the way the system processes data, by using better algorithms and data structures. We can also distribute the workload among multiple machines or processors, using techniques like parallelization and distributed computing.\nWe can reduce the time it takes to access data from storage by using caching and pre-fetching techniques. Load balancing and other techniques also help distribute the workload across multiple resources. All of these steps can improve both latency and throughput, making the system faster and more efficient.\nCaching Caching is a way to make computer systems faster at finding data. It stores data that is frequently-used in a spot (For example, Memory) that is easy to find. This spot is called cache. When the system needs the data again, it can find it faster because it's already in the cache.\nThere are three types of caching:\nMemory caching: This puts data in the computer's memory (RAM). This is faster than other types of storage. Disk caching: This puts data on a fast disk, like an SSD. This is faster than a normal hard drive. Network caching: This puts data on another nearby device, like a router or server. Other devices on the network can find it faster. Caching can help a system work better because it makes finding data faster. It can also make storage devices work less, which makes them last longer and work better by increasing the lifespan.\nProxy A proxy is a device or service that acts as a middleman between a client and a server. The proxy receives client queries, which then transmits them to the server and returns the server's response to the client.\nProxies are often used in system design to do things like:\nProvide security: A proxy can block harmful traffic and protect the server from attacks. Enhance privacy: A proxy can hide the client's IP address, making it hard for others to track their online activity. Improve performance: A proxy can save frequently-used data, reducing what the server has to do and making it faster for the client to get data. Load balance: A proxy can divide incoming requests across multiple servers, making the system more reliable and faster. Load Balancers A load balancer is a tool that shares incoming requests across different servers or resources. Load balancing aims to enhance the performance and dependability of a system by spreading the workload evenly across multiple resources.\nBenefits of using load balancers in system design include:\nBetter performance: By evenly distributing the workload, load balancers can ensure that each server or resource has enough capacity to manage its share of requests. This can improve the system's overall performance, as it can handle a greater number of requests without getting overloaded. Better reliability: By distributing the workload, load balancers can help ensure the system remains available and responsive, even if one or more servers or resources fail. This can enhance the system's reliability, as it can continue functioning despite failures. Improved scalability: Load balancers can simplify adding additional servers or resources to a system, as they can automatically distribute incoming requests across the available resources. This can make it easier to scale a system up or down, depending on its workload. Hashing In system design, hashing is a technique used to efficiently store and retrieve data. Hashing involves applying a mathematical function, called a hash function, to a data item to generate a fixed-size value, called a hash code or hash value. The hash code is then used as an index or key to store and retrieve the data item in a data structure, such as a hash table or hash map.\nHashing has several benefits in system design, including:\nEfficiency: Hashing allows for efficient data storage and retrieval, as it reduces the amount of data that must be stored and compared in order to find a specific item. This can improve the performance of a system, as it can access and manipulate data more quickly. Uniqueness: A well-designed hash function will generate unique hash codes for each data item, making it unlikely that two items will have the same hash code. This can help ensure the integrity and correctness of data in a system. Security: Hashing can be used to securely store sensitive data, such as passwords, as the hash code cannot be easily reversed to reveal the original data. This can help protect the security of a system and its users. Replication and Sharding In system design, replication and sharding are two techniques used to improve the performance, reliability, and scalability of a system. Replication involves creating multiple copies of data and storing them on different servers or devices, while sharding involves dividing a large dataset into smaller partitions and storing each partition on a different server or device.\nReplication and sharding can be used together or independently in system design, depending on the specific requirements of the system. Some of the benefits of using replication and sharding include:\nImproved performance: By storing multiple copies of data or partitioning a large dataset, a system can access and manipulate data more quickly, as it can read from or write to multiple servers or devices in parallel. This can improve the overall performance of the system. Improved reliability: By storing multiple copies of data, a system can continue to function even if one or more servers or devices fail. This can improve the reliability of the system, as it can continue to serve users and maintain data integrity. Enhanced scalability: By dividing a large dataset into smaller partitions, a system can more easily scale up or down, as it can add or remove servers or devices without having to move or redistribute the entire dataset. P2P Network In system design, a peer-to-peer (P2P) network is a type of network in which each device, or peer, has the same capabilities and functions as every other device in the network. In a P2P network, there is no central server or authority, and each peer can communicate and exchange data directly with any other peer in the network.\nP2P networks have several benefits in system design, including:\nDecentralization: P2P networks are decentralized, meaning that there is no central server or authority controlling the network. This can make P2P networks more resilient and flexible, as they can continue to function even if some peers fail or leave the network. Efficiency: In a P2P network, each peer can act as both a client and a server, allowing for more efficient data exchange. This can reduce the workload on any one peer and improve the overall performance of the network. Scalability: P2P networks can easily scale up or down, as new peers can join or leave the network without requiring any changes to the network infrastructure. This can make P2P networks well-suited to applications with a large number of users or devices. One example of a P2P network is BitTorrent, a popular file-sharing application. In BitTorrent, users can share files with each other directly, without the need for a central server. Each user's computer acts as a peer in the network, and can download and upload pieces of a file from and to other peers.\nIn BitTorrent, each peer maintains a list of other peers that it is connected to, and can exchange data directly with these peers. As a result, the network can function even if some peers are offline or leave the network, and it can easily scale up or down as new users join or leave.\nAPI Design Application programming interface (API) design is the process of establishing the interfaces and requirements for APIs in a system. An API is a set of standards and programming guidelines that specify how various system parts, or systems themselves, can communicate and share data.\nThere are several common architectures for designing APIs in software engineering, each with its own set of advantages and disadvantages. Here are some common API design architectures:\nREST (Representational State Transfer): This is a popular architectural style for designing web APIs that is based on the principles of HTTP and the RESTful web. REST APIs frequently employ HTTP methods (such GET, POST, and DELETE) to indicate the operations that can be carried out on a resource. They are created to be scalable, modular, and simple to use. SOAP (Simple Object Access Protocol): This is an older architectural style for designing web APIs that is based on the principles of XML and Web Services. SOAP APIs are typically more complex and difficult to use than REST APIs, but they can support a wider range of messaging formats and protocols, such as HTTP, SMTP, and JMS. GraphQL: This is a relatively new architectural style for designing APIs that is based on the principles of graph theory and the querying language of the same name. GraphQL APIs are designed to be flexible and efficient, and they allow clients to specify exactly the data they need, in a single request. This can make GraphQL APIs more efficient and scalable than other types of APIs. gRPC (Google Remote Procedure Call): This is an open-source framework for designing APIs that is based on the principles of RPC and Protocol Buffers. gRPC APIs are designed to be fast, efficient, and low-latency, and they use a binary encoding format to transmit data, which can make them more efficient than APIs that use text-based formats. Webhooks: This is a simple architectural style for designing APIs that is based on the principles of webhooks and real-time notifications. Webhook APIs are designed to be lightweight and easy to use, and they allow clients to register a URL to which the API can send notifications when certain events occur. This can make Webhook APIs ideal for applications that need to be notified of events in real-time. Leader Election Leader election is a common problem in distributed systems, where a group of nodes (or \u0026quot;processes\u0026quot;) need to agree on which node should be the leader. This is typically done by having each node send a message to the other nodes, announcing its intention to become the leader. The other nodes then decide which node to elect as the leader based on some predetermined criteria, such as the node's rank or its availability.\nThere are several different algorithms and strategies for implementing leader election in a distributed system. Here are some common approaches:\nBully algorithm: This is a simple algorithm where each node sends a \u0026quot;request to be leader\u0026quot; message to all of the other nodes. The other nodes reply with an \u0026quot;acknowledge\u0026quot; message if they are not themselves trying to be leader. If a node does not receive any \u0026quot;acknowledge\u0026quot; messages, it assumes that it has won the election and becomes the leader. This algorithm is easy to implement, but it can be slow and inefficient, especially in large distributed systems. Ranking algorithm: This is a more sophisticated algorithm where each node is assigned a rank, and the node with the highest rank wins the election. The rank can be determined based on factors such as the node's availability, its processing power, or its connectivity to the other nodes. This algorithm is more efficient than the bully algorithm, but it can be difficult to determine the rank of each node in a fair and unbiased way. Virtual synchrony algorithm: This is an algorithm that relies on the concept of virtual synchrony, where the nodes in a distributed system are treated as if they were executing in a synchronized manner. The leader is elected by having each node send a \u0026quot;request to be leader\u0026quot; message to a designated \u0026quot;coordinator\u0026quot; node, which then decides which node to elect as the leader based on some predetermined criteria. This algorithm is more complex than the bully or ranking algorithms, but it can be more efficient and reliable in large distributed systems. Messages and Pub-Sub In distributed systems, messages and the pub-sub (publish-subscribe) pattern can be used to facilitate communication between different components. Messages are units of data that can be sent to share information, request services, or trigger actions between different parts of a distributed system. In a pub-sub system, a \u0026quot;publisher\u0026quot; sends messages to one or more \u0026quot;subscriber\u0026quot; nodes. The publisher does not need to know which nodes are subscribed to its messages, and the subscribers do not need to know where the messages come from.\nThe pub-sub pattern has several benefits in distributed systems. It allows nodes to work together without being closely connected, and it can scale easily. Furthermore, it enables nodes to share any number of messages, regardless of the types of messages or the number of other nodes in the system.\nHowever, the pub-sub pattern also presents some challenges. One issue is ensuring that messages are delivered to the correct nodes quickly and securely. Another issue is protecting messages from being intercepted or compromised by attackers. A third issue is managing a large volume of messages, which can slow down the network and consume resources.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/system-design-fundamentals-software-engineering/","section":"software-engineering","tags":["system design","software engineering"],"title":"Core Concepts of System Design in Software Engineering"},{"body":"","link":"https://blog.sksoumik.com/series/software-engineering/","section":"series","tags":null,"title":"software engineering"},{"body":"122.Best Time to Buy and Sell Stock II You are given an integer array prices where prices[i] is the price of a given stock on the ith day.\nOn each day, you may decide to buy and/or sell the stock. You can only hold at most one share of the stock at any time. However, you can buy it then immediately sell it on the same day.\nFind and return the maximum profit you can achieve.\nExample 1:\n1Input: prices = [7,1,5,3,6,4] 2Output: 7 3Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4. 4Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3. 5Total profit is 4 + 3 = 7. Example 2:\n1Input: prices = [1,2,3,4,5] 2Output: 4 3Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. 4Total profit is 4. Example 3:\n1Input: prices = [7,6,4,3,1] 2Output: 0 3Explanation: There is no way to make a positive profit, so we never buy the stock to achieve the maximum profit of 0. 1class Solution: 2 def maxProfit(self, prices: List[int]) -\u0026gt; int: 3 # define the recursive function with memoization 4 @lru_cache(None) 5 def recursion(time, stock): 6 # base case: if we have reached the end of the prices list, return 0 7 if time \u0026gt;= len(prices): return 0 8 9 # if we don\u0026#39;t have any stock, we can buy it 10 buy = -prices[time] + recursion(time+1, stock+1) if stock == 0 else float(\u0026#34;-inf\u0026#34;) 11 12 # if we have stock, we can sell it 13 sell = prices[time] + recursion(time+1, stock-1) if stock == 1 else float(\u0026#34;-inf\u0026#34;) 14 15 # we can also choose to hold onto the stock 16 hold = 0 + recursion(time+1, stock) 17 18 # return the maximum profit from the three options 19 return max(buy, sell, hold) 20 21 # start the recursion from time 0 and no stock 22 return recursion(0, 0) 121. Best Time to Buy and Sell Stock You are given an array prices where prices[i] is the price of a given stock on the ith day.\nYou want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.\nReturn the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.\nExample 1:\n1Input: prices = [7,1,5,3,6,4] 2Output: 5 3Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. 4Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell. Example 2:\n1Input: prices = [7,6,4,3,1] 2Output: 0 3Explanation: In this case, no transactions are done and the max profit = 0. 1class Solution: 2 def maxProfit(self, prices): 3 @lru_cache(None) 4 # This is a decorator that applies memoization to the `recursion` function 5 # so that it will only compute values for a given time and stock state once, 6 # and then store the result in a cache so that it can be looked up later. 7 # This helps improve the performance of the function by avoiding redundant 8 # calculations. 9 10 # time=idx; time indicates the curret index 11 # the current time represented by an index, the current stock state 12 # (either 0 for not having stock or 1 for having stock), 13 def recursion(time, stock, count): 14 # This is the main recursive function that computes the maximum profit 15 # possible given the current time, stock state, and count of transactions. 16 17 # If the number of transactions is greater than 1, we can\u0026#39;t make any more 18 # transactions, so we return 0. 19 if k \u0026gt; 1: return 0 20 21 # If the time is greater than or equal to the length of the prices list, 22 # then we have reached the end of the prices, so we return 0. 23 if time \u0026gt;= len(prices): return 0 24 25 # If we don\u0026#39;t have any stock, we can buy one at the current time and price. 26 buy = -prices[time] + recursion(time + 1, stock + 1, count) if stock == 0 else float(\u0026#34;-inf\u0026#34;) 27 28 # If we have stock, we can sell it at the current time and price. 29 # stock (stock = 1) to not having stock (stock = 0). Therefore, 30 # the stock value should be set to 0 in the recursive call. 31 # Since stock is currently 1, setting stock + 1 will result in a value of 0, 32 # which is the correct value for the new stock state after the sell action. 33 sell = prices[time] + recursion(time + 1, stock + 1, count+1) if stock == 1 else float(\u0026#34;-inf\u0026#34;) 34 35 # If we neither buy nor sell, we hold our current stock. 36 hold = 0 + recursion(time + 1, stock, count) 37 38 # Return the maximum of the three possible actions: buy, sell, or hold. 39 return max(buy, sell, hold) 40 41 # Set the maximum number of transactions to 1. 42 # day intervel is max 1 43 k = 1 44 45 # Call the `recursion` function with the initial time, stock state, and count. 46 return recursion(0, 0, 0) The time complexity of the above code is O(n) because the recursion function is called once for each element in the prices list.\nThe space complexity of the above code is O(n) because the recursion function stores the result of each calculation in a cache, which grows in size as the function is called more times. Each time the function is called, a new result is added to the cache, so the size of the cache is at most the same as the size of the prices list.\n123. Best Time to Buy and Sell Stock III You are given an array prices where prices[i] is the price of a given stock on the ith day.\nFind the maximum profit you can achieve. You may complete at most two transactions.\nNote: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).\nExample 1:\n1Input: prices = [3,3,5,0,0,3,1,4] 2Output: 6 3Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3. 4Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3. Example 2:\n1Input: prices = [1,2,3,4,5] 2Output: 4 3Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. 4Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are engaging multiple transactions at the same time. You must sell before buying again. Example 3:\n1Input: prices = [7,6,4,3,1] 2Output: 0 3Explanation: In this case, no transaction is done, i.e. max profit = 0. 1class Solution: 2 def maxProfit(self, prices: List[int]) -\u0026gt; int: 3 @lru_cache(None) 4 def recursion(time, stock, count): 5 if count \u0026gt;= k: return 0 6 if time \u0026gt;= len(prices): return 0 7 8 buy = -prices[time] + recursion(time + 1, stock + 1, count) if stock == 0 else float(\u0026#34;-inf\u0026#34;) 9 sell = prices[time] + recursion(time + 1, stock - 1, count+1) if stock == 1 else float(\u0026#34;-inf\u0026#34;) 10 hold = 0 + recursion(time + 1, stock, count) 11 12 return max(buy, sell, hold) 13 14\t# day intervel is max 2 15 k = 2 16 return recursion(0, 0, 0) The time complexity of the above code is O(n), where n is the length of the prices list. This is because the lru_cache decorator is used to store the results of recursive calls in a dictionary, so that each recursive call is only computed once.\nThe space complexity of the above code is O(n), as the number of recursive calls is at most n, and each call requires O(1) space to store in the cache.\n188. Best Time to Buy and Sell Stock IV You are given an integer array prices where prices[i] is the price of a given stock on the ith day, and an integer k.\nFind the maximum profit you can achieve. You may complete at most k transactions.\nNote: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).\nExample 1:\n1Input: k = 2, prices = [2,4,1] 2Output: 2 3Explanation: Buy on day 1 (price = 2) and sell on day 2 (price = 4), profit = 4-2 = 2. Example 2:\n1Input: k = 2, prices = [3,2,6,5,0,3] 2Output: 7 3Explanation: Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6-2 = 4. Then buy on day 5 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3. 1class Solution: 2 def maxProfit(self, k: int, prices: List[int]) -\u0026gt; int: 3 @lru_cache(None) 4 def recursion(time, stock, count): 5 6 if count \u0026gt;= k: return 0 7 if time \u0026gt;= len(prices): return 0 8 9 buy = -prices[time] + recursion(time + 1, stock + 1, count) if stock == 0 else float(\u0026#34;-inf\u0026#34;) 10 sell = prices[time] + recursion(time + 1, stock - 1, count+1) if stock == 1 else float(\u0026#34;-inf\u0026#34;) 11 hold = 0 + recursion(time + 1, stock, count) 12 13 return max(buy, sell, hold) 14 return recursion(0, 0, 0) 309.Best Time to Buy and Sell Stock with Cooldown You are given an array prices where prices[i] is the price of a given stock on the ith day.\nFind the maximum profit you can achieve. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times) with the following restrictions:\nAfter you sell your stock, you cannot buy stock on the next day (i.e., cooldown one day). Note: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).\nExample 1:\n1Input: prices = [1,2,3,0,2] 2Output: 3 3Explanation: transactions = [buy, sell, cooldown, buy, sell] Example 2:\n1Input: prices = [1] 2Output: 0 1class Solution: 2 def maxProfit(self, prices: List[int]) -\u0026gt; int: 3 @lru_cache(None) 4 def recursion(time, stock): 5 6 if time \u0026gt;= len(prices): 7 return 0 8 9 buy = -prices[time] + recursion(time + 1, stock + 1) if stock == 0 else float(\u0026#34;-inf\u0026#34;) 10 11 # time + 2 is used to represent the \u0026#34;cool down\u0026#34; period that must be waited after selling a stock. 12 # In this line, the function is selling a stock and then skipping the next day (time + 1) 13 # because it is the cool down period. 14 sell = prices[time] + recursion(time + 2, stock - 1) if stock == 1 else float(\u0026#34;-inf\u0026#34;) 15 hold = 0 + recursion(time + 1, stock) 16 17 return max(buy, sell, hold) 18 19 return recursion(0, 0) 714. Best Time to Buy and Sell Stock with Transaction Fee You are given an array prices where prices[i] is the price of a given stock on the ith day, and an integer fee representing a transaction fee.\nFind the maximum profit you can achieve. You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction.\nNote: You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again).\nExample 1:\n1Input: prices = [1,3,2,8,4,9], fee = 2 2Output: 8 3Explanation: The maximum profit can be achieved by: 4- Buying at prices[0] = 1 5- Selling at prices[3] = 8 6- Buying at prices[4] = 4 7- Selling at prices[5] = 9 8The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8. Example 2:\n1Input: prices = [1,3,7,5,10,3], fee = 3 2Output: 6 1class Solution: 2 def maxProfit(self, prices: List[int], fee: int) -\u0026gt; int: 3 # Use lru_cache as a decorator to cache the results of the recursive function. 4 # This will store the return values of the function using the arguments as a key, 5 # so that if the function is called with the same arguments again, 6 # it can return the stored value instead of recalculating it. 7 # The parameter \u0026#39;None\u0026#39; means that the cache has no fixed size and will continue to grow as needed. 8 @lru_cache(None) 9 def recursion(time, stock): 10 # base case: if we have reached the end of the prices list, return 0 profit 11 if time \u0026gt;= len(prices): 12 return 0 13 14 # if we don\u0026#39;t have any stock, we can buy one 15 # the cost of buying is the price at the current time minus the profit from the next action 16 # (either selling at a later time or holding the stock) 17 buy = -prices[time] + recursion(time+1, stock+1) if stock == 0 else float(\u0026#34;-inf\u0026#34;) 18 19 # if we have stock, we can sell it 20 # the profit from selling is the price at the current time 21 # plus the fee for selling minus the profit from the next action 22 sell = prices[time] - fee + recursion(time+1, stock-1) if stock == 1 else float(\u0026#34;-inf\u0026#34;) 23 24 # if we don\u0026#39;t want to buy or sell, we can hold on to our stock 25 # holding onto the stock means no change in profit 26 hold = 0 + recursion(time+1, stock) 27 28 # return the maximum profit of the three options 29 return max(buy, sell, hold) 30 31 # start the recursion at time 0 with no stock 32 return recursion(0,0) Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/best_time_to_buy_all_sell_stocks_all_problems/","section":"software-engineering","tags":["algorithms","leetcode","problem solving"],"title":"All 6 Best Time to Buy and Sell Stock Problems Using The Same Formula"},{"body":"Spark MLlib MLlib is a library for machine learning in Spark that aims to make it easy to use and scalable for practical applications. It includes tools for common ML tasks, such as classification, regression, clustering, and collaborative filtering, as well as featurization methods for feature extraction, transformation, dimensionality reduction, and selection. MLlib also provides tools for building, evaluating, and tuning ML pipelines, as well as utilities for linear algebra, statistics, and data handling.\nSpark offers APIs in several languages, including Java, Scala, Python, and R. These APIs allow developers to use Spark's powerful distributed computing capabilities to build a wide variety of applications, including data processing pipelines, machine learning models, and real-time streaming applications.\nFor this tutorial, we are going to use the Python API, which is called PySpark.\nRead more about it from the official documentation.\nWhy using Spark over other ML frameworks? One of the main benefits of using Spark MLlib over Tensorflow or PyTorch or any other ML frameworks is that it is designed to be highly scalable and efficient, especially when working with large datasets. Spark's distributed computing engine allows MLlib to distribute training and inference processes across multiple machines, which can significantly speed up the training and evaluation of machine learning models. Additionally, MLlib includes a wide range of machine learning algorithms and utilities, which makes it a convenient and comprehensive tool for building and deploying machine learning models in a production environment. By comparison, Tensorflow and PyTorch are primarily focused on providing low-level building blocks for developing machine learning models, and do not include as many high-level tools for distributed training and deployment.\nProject Goal In this project, we will make a classifier that can classify sentences into different emotions. As a target label, we have six different types of emotions:\n1[joy, love, anger, fear, surprise, sadness] Given an input text, our Machine Learning model should be able to tell which emotion the sentence expresses.\nDataset The dataset we will use in this tutorial is emotional dataset for NLP and can be downloaded from Kaggle.\nInstall Dependencies 1!pip install pyspark 2!pip install pandas Load the data The dataset is divided into train test and validation, and all are in txt format. We will only use the train.txtfile. Let's first load the data using Pandas and convert it to csv so that we can load it using Spark. You can use Jupyter notebook or Google Colab executing the following code.\n1import pandas as pd 2 3# set panas to print full text 4pd.set_option(\u0026#34;display.max_columns\u0026#34;, None) 5pd.set_option(\u0026#34;display.max_rows\u0026#34;, None) 6pd.set_option(\u0026#34;display.max_colwidth\u0026#34;, None) 7 8# hide pyspark warnings 9import warnings 10warnings.filterwarnings(\u0026#34;ignore\u0026#34;) 11 12# read the data which is in txt format 13df = pd.read_csv(\u0026#34;train.txt\u0026#34;, names=[\u0026#34;text\u0026#34;, \u0026#34;emotion\u0026#34;], delimiter=\u0026#34;;\u0026#34;) 14 15# let\u0026#39;s save the data in csv format with header 16df.to_csv(\u0026#34;train.csv\u0026#34;, index=False, header=True) 17 18# print the first 2 rows 19df.head(2) output\ntext emotion 0 i didnt feel humiliated sadness 1 i can go from feeling so hopeless to so damned hopeful just from being around someone who cares and is awake sadness Create Spark Session\n1# import all the required libraries for creating spark session 2import pyspark 3from pyspark import SparkContext 4from pyspark.sql import SparkSession 5 6# Create a SparkContext instance 7spark_context = SparkContext(master=\u0026#34;local\u0026#34;) 8 9# Creating a spark session. 10spark = SparkSession.builder.appName(\u0026#34;Emotion Detection\u0026#34;).getOrCreate() 11print(spark_context) This will print something like below:\n1SparkContext 2 3Spark UI 4 5 Version 6 v3.3.1 7 Master 8 local 9 AppName 10 pyspark-shell Load CSV data using Spark 1# read the train.csv file 2df = spark.read.csv(\u0026#34;train.csv\u0026#34;, header=True, inferSchema=True) 3df.show(2) output\n1+--------------------+-------+ 2| text|emotion| 3+--------------------+-------+ 4|i didnt feel humi...|sadness| 5|i can go from fee...|sadness| 6+--------------------+-------+ Basic EDA Let's see some facts about out data.\n1# show how many null values are there in each column using pandas 2df.toPandas().isnull().sum() output\n1text 0 2emotion 0 3dtype: int64 code\n1# display the data distribution of each emotion 2df.groupBy(\u0026#34;emotion\u0026#34;).count().show() output\n1+--------+-----+ 2| emotion|count| 3+--------+-----+ 4| joy| 5362| 5| love| 1304| 6| anger| 2159| 7| fear| 1937| 8|surprise| 572| 9| sadness| 4666| 10+--------+-----+ Feature Engineering Feature engineering is a crucial step in the process of building machine learning models. The reason for this is that most machine learning algorithms require input data in the form of numeric features, and therefore, we need to convert any categorical data into a numerical representation. In natural language processing, there are several techniques that can be used to convert text data into numerical features, such as CountVectorizer, BagOfWords, TFIDF, OneHotEncoder, WordEmbeddings, and HashingTF. To build a model that can predict a given outcome, we can create a pipeline that includes these feature engineering methods, as well as the machine learning model that will be used for prediction. The pipeline will take care of converting the raw input data into a format that the model can understand, making it easier to build and deploy machine learning models in a production environment.\ncode\n1from pyspark.ml.feature import ( 2 Tokenizer, 3 StopWordsRemover, 4 CountVectorizer, 5 IDF, 6 StringIndexer, 7) 8 9# tokenize the text column 10tokenizer = Tokenizer(inputCol=\u0026#34;text\u0026#34;, outputCol=\u0026#34;words\u0026#34;) 11 12# remove the stop words 13stopwords_remover = StopWordsRemover( 14 inputCol=tokenizer.getOutputCol(), outputCol=\u0026#34;filtered\u0026#34; 15) 16 17# convert the words to vectors 18count_vectorizer = CountVectorizer( 19 inputCol=stopwords_remover.getOutputCol(), outputCol=\u0026#34;raw_features\u0026#34; 20) 21 22# calculate the idf 23idf = IDF(inputCol=count_vectorizer.getOutputCol(), outputCol=\u0026#34;features\u0026#34;) 24 25 26# convert the emotion column to label 27label_stringIdx = StringIndexer(inputCol=\u0026#34;emotion\u0026#34;, outputCol=\u0026#34;label\u0026#34;) 28 29# import the required libraries for creating the pipeline 30from pyspark.ml import Pipeline 31 32# create the pipeline 33pipeline = Pipeline( 34 stages=[tokenizer, stopwords_remover, count_vectorizer, idf, label_stringIdx] 35) 36 37# fit the pipeline to the data 38pipeline_fit = pipeline.fit(df) 39 40# transform the data 41dataset = pipeline_fit.transform(df) 42 43# display the first 2 rows 44dataset.show(2) output\n1+--------------------+-------+--------------------+--------------------+--------------------+--------------------+-----+ 2| text|emotion| words| filtered| raw_features| features|label| 3+--------------------+-------+--------------------+--------------------+--------------------+--------------------+-----+ 4|i didnt feel humi...|sadness|[i, didnt, feel, ...|[didnt, feel, hum...|(15082,[0,48,567]...|(15082,[0,48,567]...| 1.0| 5|i can go from fee...|sadness|[i, can, go, from...|[go, feeling, hop...|(15082,[1,29,42,5...|(15082,[1,29,42,5...| 1.0| 6+--------------------+-------+--------------------+--------------------+--------------------+--------------------+-----+ code\n1# show which emotions belongs to which encoded label 2dataset.select(\u0026#34;emotion\u0026#34;, \u0026#34;label\u0026#34;).distinct().show() output\n1+--------+-----+ 2| emotion|label| 3+--------+-----+ 4| love| 4.0| 5| joy| 0.0| 6|surprise| 5.0| 7| anger| 2.0| 8| sadness| 1.0| 9| fear| 3.0| 10+--------+-----+ code\n1# split the data into train and test 2train, test = dataset.randomSplit([0.7, 0.3], seed=100) 3 4# print the number of rows in train and test 5print(\u0026#34;Training Dataset Count: \u0026#34; + str(train.count())) 6print(\u0026#34;Test Dataset Count: \u0026#34; + str(test.count())) output\n1Training Dataset Count: 11246 2Test Dataset Count: 4754 Build Model 1 code\n1# build the model 2from pyspark.ml.classification import NaiveBayes 3from pyspark.ml.evaluation import MulticlassClassificationEvaluator 4 5 6# create the model 7nb = NaiveBayes(featuresCol=\u0026#34;features\u0026#34;, labelCol=\u0026#34;label\u0026#34;, predictionCol=\u0026#34;prediction\u0026#34;) 8 9# fit the model to the train data 10model = nb.fit(train) 11 12# predict the test data 13predictions = model.transform(test) 14 15# create the evaluator 16evaluator = MulticlassClassificationEvaluator( 17 labelCol=\u0026#34;label\u0026#34;, predictionCol=\u0026#34;prediction\u0026#34;, metricName=\u0026#34;accuracy\u0026#34; 18) 19 20# calculate the accuracy 21accuracy = evaluator.evaluate(predictions) 22print(\u0026#34;Test set accuracy = \u0026#34; + str(accuracy)) 23 24# create the evaluator 25evaluator = MulticlassClassificationEvaluator( 26 labelCol=\u0026#34;label\u0026#34;, predictionCol=\u0026#34;prediction\u0026#34;, metricName=\u0026#34;f1\u0026#34; 27) 28 29# calculate the f1 score 30f1_score = evaluator.evaluate(predictions) 31print(\u0026#34;Test set f1 score = \u0026#34; + str(f1_score)) output\n1Test set accuracy = 0.6903660075725705 2Test set f1 score = 0.703776482543677 So, we are getting around 70% f1-score.\nLet's see some correct predictions.\ncode\n1# some predictions that are correct display only the emotion, label, prediction 2predictions.filter(predictions.label == predictions.prediction).select( 3 \u0026#34;emotion\u0026#34;, \u0026#34;label\u0026#34;, \u0026#34;prediction\u0026#34; 4).show(5) output\n1+-------+-----+----------+ 2|emotion|label|prediction| 3+-------+-----+----------+ 4| fear| 3.0| 3.0| 5| fear| 3.0| 3.0| 6|sadness| 1.0| 1.0| 7| joy| 0.0| 0.0| 8| anger| 2.0| 2.0| 9+-------+-----+----------+ Let's see how many are correct predictions and how many are wrong predictions.\n1# how many predictions are correct and how many are wrong 2predictions.filter(predictions.label == predictions.prediction).count() # outputs 3282 3 4# how many predictions are correct and how many are wrong 5predictions.filter(predictions.label != predictions.prediction).count() # outputs 1472 Build Model 2 Let's try another simple model.\n1# Logistic Regression 2from pyspark.ml.classification import LogisticRegression 3 4# create the model 5lr = LogisticRegression( 6 featuresCol=\u0026#34;features\u0026#34;, labelCol=\u0026#34;label\u0026#34;, predictionCol=\u0026#34;prediction\u0026#34; 7) 8 9# fit the model to the train data 10model = lr.fit(train) 11 12# predict the test data 13predictions = model.transform(test) 14 15# create the evaluator 16evaluator = MulticlassClassificationEvaluator( 17 labelCol=\u0026#34;label\u0026#34;, predictionCol=\u0026#34;prediction\u0026#34;, metricName=\u0026#34;accuracy\u0026#34; 18) 19 20# calculate the accuracy 21accuracy = evaluator.evaluate(predictions) 22 23# print the accuracy 24print(\u0026#34;Test set accuracy = \u0026#34; + str(accuracy)) 25 26# create the evaluator 27evaluator = MulticlassClassificationEvaluator( 28 labelCol=\u0026#34;label\u0026#34;, predictionCol=\u0026#34;prediction\u0026#34;, metricName=\u0026#34;f1\u0026#34; 29) 30 31# calculate the f1 score 32f1_score = evaluator.evaluate(predictions) 33 34# print the f1 score 35print(\u0026#34;Test set f1 score = \u0026#34; + str(f1_score)) output\n1Test set accuracy = 0.8300378628523348 2Test set f1 score = 0.8317629919611031 We can see that the accuracy and f1 have been increased by using LogisticRegression than the NaiveBayes.\nYou can try out other models as well. See all the available models that comes with Spark MLlib for classification and regression.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/multi-class-text-classification-apache-spark-mllib/","section":"artificial-intelligence","tags":["project-tutorial","NLP","spark","machine learning"],"title":"Multi-class Text Classification Using Apache Spark MLlib"},{"body":"","link":"https://blog.sksoumik.com/tags/spark/","section":"tags","tags":null,"title":"spark"},{"body":"Keyphrases are important pieces of information that can be extracted from text documents. These are words or phrases that summarize the main ideas or topics of a text, and they can be useful for a variety of applications, such as document summarization, text classification, and information retrieval. In this blog post, we will explore how keyphrases can be extracted from text documents, and discuss some of the techniques and tools that can be used for this task.\nOne of the most common ways to extract keyphrases from text is to use a technique called keyword extraction. This involves identifying the most important words or phrases in a text, based on their frequency, relevance, or other criteria. There are several algorithms and tools that can be used for keyword extraction, including term frequency-inverse document frequency (TF-IDF), Latent Semantic Indexing (LSI), and topic modeling. These methods can be used to identify the words or phrases that are most relevant to the topic of the text, and to filter out common words or stop words that are not useful for keyphrase extraction.\nAnother approach to keyphrase extraction is to use natural language processing (NLP) techniques. NLP is a field of artificial intelligence that focuses on understanding and processing human language. It includes a wide range of techniques, such as part-of-speech tagging, syntactic parsing, and semantic analysis, that can be used to identify the structure and meaning of a text. By using NLP techniques, it is possible to extract keyphrases from a text by identifying the noun phrases, verb phrases, or other important parts of the text.\nOne of the most popular tools for keyphrase extraction is the KeyBERT model, which is a state-of-the-art language model developed by researchers at the Allen Institute for Artificial Intelligence. KeyBERT is trained on a large corpus of text data, and uses a combination of deep learning and NLP techniques to identify the keyphrases in a text. It can extract keyphrases from a wide range of texts, including news articles, scientific papers, and social media posts, and it is highly accurate and efficient at identifying the most important phrases in a text.\nIn this blog post, I am going to show how to extract keyphrases using KeyBERT at first, and then show how we can use part-of-speech patterns to extract grammatically correct keyphrases.\nLet's first load our data. The dataset can be downloaded from Kaggle. I am using Jupyter Notebook to execute my codes.\nInstall Dependencies 1pip install pandas 2pip install keybert 3pip install keyphrase-vectorizers Load Dataset 1import pandas as pd 2 3# set pandas setting to display full dataframe 4pd.set_option(\u0026#39;display.max_columns\u0026#39;, None) 5pd.set_option(\u0026#39;display.max_rows\u0026#39;, None) 6pd.set_option(\u0026#39;display.max_colwidth\u0026#39;, None) 7 8 9df = pd.read_json(\u0026#39;News_Category_Dataset_v3.json\u0026#39;, lines=True) 10df.head(3) output (our dataset looks like below)\nlink headline category short_description authors date 0 https://www.huffpost.com/entry/covid-boosters-uptake-us_n_632d719ee4b087fae6feaac9 Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters U.S. NEWS Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. Carla K. Johnson, AP 2022-09-23 1 https://www.huffpost.com/entry/american-airlines-passenger-banned-flight-attendant-punch-justice-department_n_632e25d3e4b0e247890329fe American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video U.S. NEWS He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. Mary Papenfuss 2022-09-23 2 https://www.huffpost.com/entry/funniest-tweets-cats-dogs-september-17-23_n_632de332e4b0695c1d81dc02 23 Of The Funniest Tweets About Cats And Dogs This Week (Sept. 17-23) COMEDY \u0026quot;Until you have a dog you don't understand what could be eaten.\u0026quot; Elyse Wanshel 2022-09-23 But we only need some of these columns; we will only extract KeyPhrases/Keywords from the headline and short_description columns. But first, I am going to merge both of these columns.\n1df = df[[\u0026#39;headline\u0026#39;, \u0026#39;short_description\u0026#39;]] 2 3# merge headline and short description into one column 4df[\u0026#39;content\u0026#39;] = df[\u0026#39;headline\u0026#39;] + \u0026#39;. \u0026#39; + df[\u0026#39;short_description\u0026#39;] 5 6df.head(2) output\nheadline short_description content 0 Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters. Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. 1 American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video. He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. Now, I will use the content column only.\nWe will extract keyphrases only from the top 50 rows. As this is only for demonstration purposes.\n1# take only the first 50 rows 2df = df[:50] Text Cleaning In general, text cleaning is not necessary for transformer-based language models, as they are designed to handle a wide range of input text. However, depending on the specific application, there may be cases where some form of text cleaning can improve the performance of the model.\nFor example, if the input text contains a lot of noisy or irrelevant information, such as HTML tags or URLs, then removing this information could help the model focus on the relevant content and improve its performance. Additionally, if the input text is not in the correct format for the model (e.g. the text should be lowercase but the input text is not), then cleaning the text to correct this formatting could also improve the model's performance. In short, text cleaning is not always necessary for transformer-based language models, but in some cases it can be helpful. It is worth considering whether text cleaning could improve the performance of your model, and if so, implementing the appropriate cleaning steps.\nIn our case, we are going to skip the text cleaning step.\nKeyword/Keyphrase Extraction Without POS (part-of-speech) Pattern I will use KeyBERT. It uses BERT-embeddings and simple cosine similarity to find the sub-phrases in a document that are the most similar to the document itself.\nFirst, document embeddings are extracted with BERT to get a document-level representation. Then, word embeddings are extracted for N-gram words/phrases. Finally, we use cosine similarity to find the words/phrases that are the most similar to the document. The most similar words could then be identified as the words that best describe the entire document.\n1from keybert import KeyBERT 2 3# create a KeyBERT model object that can be used to extract keyphrases 4model = KeyBERT() 5 6def get_keyphrase_bert(text): 7 # extract keyphrases from the text using the pre-defined KeyBERT model 8 keyphrase = model.extract_keywords(text, keyphrase_ngram_range=(1, 3), stop_words=\u0026#39;english\u0026#39;, top_n=5) 9 return keyphrase 10 11df[\u0026#39;keyphrase_without_pos\u0026#39;] = df[\u0026#39;content\u0026#39;].apply(get_keyphrase_bert) Now if we print the dataframe, it looks like below:\nheadline short_description content keyphrase_without_pos 0 Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters. Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. [(omicron targeted covid, 0.6294), (targeted covid boosters, 0.6243), (sleeves omicron targeted, 0.579), (covid boosters, 0.5739), (covid boosters health, 0.565)] 1 American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video. He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. [(punching flight attendant, 0.6868), (flyer charged banned, 0.6703), (airlines flyer charged, 0.6263), (punching flight, 0.5982), (american airlines flyer, 0.5614)] keyphrase_without_pos column has our top 5 keyphrases with their similarity score.\nLet's only extract the keyphrases without the scores, so that we can obserbe their structure better.\n1from keybert import KeyBERT 2 3# create a KeyBERT model object that can be used to extract keyphrases 4model = KeyBERT() 5 6def get_keyphrase_bert(text): 7 # extract keyphrases from the text using the pre-defined KeyBERT model 8 keyphrase = model.extract_keywords(text, keyphrase_ngram_range=(1, 3), stop_words=\u0026#39;english\u0026#39;, top_n=5) 9 return [i[0] for i in keyphrase] 10 11df[\u0026#39;keyphrase_without_pos\u0026#39;] = df[\u0026#39;content\u0026#39;].apply(get_keyphrase_bert) Let's only display the content and keyphrase_without_pos column.\n1df[[\u0026#39;content\u0026#39;, \u0026#39;keyphrase_without_pos\u0026#39;]].head(3) output\ncontent keyphrase_without_pos 0 Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters. Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. [omicron targeted covid, targeted covid boosters, sleeves omicron targeted, covid boosters, covid boosters health] 1 American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video. He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. [punching flight attendant, flyer charged banned, airlines flyer charged, punching flight, american airlines flyer] 2 23 Of The Funniest Tweets About Cats And Dogs This Week (Sept. 17-23). \u0026quot;Until you have a dog you don't understand what could be eaten.\u0026quot; [tweets cats dogs, funniest tweets cats, tweets cats, 23 funniest tweets, cats dogs] If you notice the keyphrases, they are good, but some of them are not grammatically correct. I will show you how to apply the POS pattern in the KeyBERT model object to get more grammatically correct keyphrases. Another problem with the above approach is that we need to define the keyphrase_ngram_range. In our case, it was (1, 3). But it's tough to know which keyphrase_ngram_range will work best without a rigorous experiment.\nKeyword/Keyphrase Extraction Using POS (part-of-speech) Pattern To use POS pattern with our model, I am going to use the KeyphraseVectorizers library.\n1from keyphrase_vectorizers import KeyphraseCountVectorizer 2from keybert import KeyBERT 3 4kw_model = KeyBERT() 5 6def get_keyPhrases_POS_BERT(text): 7 keyPhrases = kw_model.extract_keywords(docs=text, vectorizer=KeyphraseCountVectorizer(), top_n=5) 8 # return only the keyphrases 9 return [keyPhrases[i][0] for i in range(len(keyPhrases))] Now, let's apply the get_keyPhrases_POS_BERT function to our content column like before, and see the result side by side.\n1df[\u0026#39;keyphrase_with_pos\u0026#39;] = df[\u0026#39;content\u0026#39;].progress_apply(get_keyPhrases_POS_BERT) 2df[[\u0026#39;content\u0026#39;, \u0026#39;keyphrase_without_pos\u0026#39;, \u0026#39;keyphrase_with_pos\u0026#39;]].head(5) output\ncontent keyphrase_without_pos keyphrase_with_pos 0 Over 4 Million Americans Roll Up Sleeves For Omicron-Targeted COVID Boosters. Health experts said it is too early to predict whether demand would match up with the 171 million doses of the new boosters the U.S. ordered for the fall. [omicron targeted covid, targeted covid boosters, sleeves omicron targeted, covid boosters, covid boosters health] [targeted covid boosters, new boosters, omicron, sleeves, doses] 1 American Airlines Flyer Charged, Banned For Life After Punching Flight Attendant On Video. He was subdued by passengers and crew when he fled to the back of the aircraft after the confrontation, according to the U.S. attorney's office in Los Angeles. [punching flight attendant, flyer charged banned, airlines flyer charged, punching flight, american airlines flyer] [punching flight attendant, american airlines flyer charged, aircraft, passengers, confrontation] 2 23 Of The Funniest Tweets About Cats And Dogs This Week (Sept. 17-23). \u0026quot;Until you have a dog you don't understand what could be eaten.\u0026quot; [tweets cats dogs, funniest tweets cats, tweets cats, 23 funniest tweets, cats dogs] [funniest tweets, cats, dogs, dog, week] 3 The Funniest Tweets From Parents This Week (Sept. 17-23). \u0026quot;Accidentally put grown-up toothpaste on my toddler’s toothbrush and he screamed like I was cleaning his teeth with a Carolina Reaper dipped in Tabasco sauce.\u0026quot; [funniest tweets parents, toddler toothbrush screamed, toothpaste toddler, toothbrush screamed, grown toothpaste toddler] [funniest tweets, tabasco sauce, toothpaste, teeth, parents] 4 Woman Who Called Cops On Black Bird-Watcher Loses Lawsuit Against Ex-Employer. Amy Cooper accused investment firm Franklin Templeton of unfairly firing her and branding her a racist after video of the Central Park encounter went viral. [watcher loses lawsuit, cops black bird, black bird watcher, amy cooper accused, bird watcher loses] [amy cooper, black bird, lawsuit, watcher, woman] If you notice the difference between keyphrase_without_pos and keyphrase_with_pos, you will see key phrases/keywords in the last column are grammatically correct. Moreover, we did not have to define the n_gram range specifically.\nCode You can find all the codes from this GitHub repository.\nThanks for the read.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/keyphrase-extraction-techniques-with-bert-embeddings-pos-patterns/","section":"artificial-intelligence","tags":["project-tutorial","programming","NLP","machine learning","python"],"title":"Keyphrase Extraction with BERT Embeddings and Part-Of-Speech Patterns"},{"body":"","link":"https://blog.sksoumik.com/beyond-code/","section":"beyond-code","tags":null,"title":"Beyond-codes"},{"body":"","link":"https://blog.sksoumik.com/tags/business/","section":"tags","tags":null,"title":"business"},{"body":"","link":"https://blog.sksoumik.com/categories/business/","section":"categories","tags":null,"title":"business"},{"body":"MVP (Minimum Viable Product) The idea of a Minimum Viable Product (MVP) is often used to test a business idea and get feedback from customers, without spending too much time and money on creating a full product.\nBurn Rate The term \u0026quot;burn rate\u0026quot; means how quickly a company uses its money. It's usually calculated every month. To find the burn rate, you add up how much the company spends in a specific time period and divide it by how many months that period is. Startups care a lot about their burn rate because it shows how fast they're using up their resources, like money they saved or investments they got. If the burn rate is high, people might worry that the company doesn't have enough money and is spending more than it's making. If the burn rate is low, people might think the company is being careful with its money and has more time before it needs more funding. It's important to remember that the burn rate can be different depending on how big the company is and what industry it's in.\nPivot In simple terms, a pivot means making a big change in how a business operates or what it offers. Startups often do this when they need to adjust their plans because of changes in the market or their own difficulties. For example, they might change who they are trying to sell to, how they sell their product, or even what their product is.\nThere are many reasons why a startup might choose to pivot. One common reason is that their product or service isn't doing as well as they hoped it would. In that case, they might pivot to stay in business and grow. On the other hand, a startup might choose to pivot to take advantage of new opportunities or to better align their operations with their goals.\nPivoting is risky because it means changing a lot about how a company works. It can be expensive and take a lot of time. But if it's done well, a pivot can help a company succeed by making it more competitive and increasing its chances of success.\nUnicorn This term is used to describe a private startup company that is valued at one billion dollars or more. A venture capitalist named Aileen Lee popularized this term in 2013. She used it to categorize distinctive and valuable tech companies that had achieved the impressive status of a billion-dollar valuation\nExit In business, \u0026quot;exit\u0026quot; means when a company or investor sells or gives up their ownership in a venture. There are different ways to exit:\nAcquisition: When another company buys a business. The owners of the business being acquired get paid once for their part of ownership. Initial Public Offering (IPO): When a private company sells shares on a stock exchange and becomes public. This allows the founders and early investors to sell their shares and exit. Secondary Market Sale: When a company or investor sells their ownership in a private company to someone else without an IPO. Liquidation: When a company sells its assets and divides the money among the shareholders. This is done when a company can't continue to operate. Venture Capital Venture capital is a type of private funding for businesses that are just starting out and have a high potential for growth. The investors who provide venture capital offer financial support, skills, and resources to these businesses in exchange for a share of ownership.\nFor startups, venture capital is an important source of funding, especially for those that can't get traditional financing like bank loans or public offerings. Venture capitalists usually invest in companies that are in the seed, early, or growth stages and have a good chance of giving a high return on investment.\nVenture capitalists don't just give money and walk away. They play an active role in the companies they invest in, providing advice, guidance, and connections to help them grow and succeed. In return, they expect a share of the profits or a bigger ownership stake in the company.\nSeed funding is the initial stage of investment in a startup. It's usually provided by angel investors or dedicated seed funds. Seed funding is used to help a startup develop its product, conduct market research, and build a team. It's also used to attract more investors and raise more money.\nAngel Group An angel group is a group of wealthy investors who pool their resources to invest in early-stage startups. They offer more than just money, providing industry insights, networking, and mentorship to help the companies they invest in succeed. For startups that are not yet ready for venture capital funding or need smaller amounts of capital, angel groups can be a great source of funding. Although the investments made by angel groups are smaller than those made by venture capital firms, they are often willing to take on higher risks for the potential of higher returns.\nSeed Fund A seed fund is a type of investment that provides financial support to startups in the early stages. In return, the fund gets a portion of the company's ownership. Usually, seed funds invest in businesses that are just starting out, often at the concept or prototype stage.\nSeed funds are important for startups that need money to grow. They invest smaller amounts than venture capital firms and are willing to take on more risk in hopes of making more money.\nSeed funds can take many forms. They can be standalone investment firms, part of accelerators or incubators, or a part of larger venture capital firms. Seed funds not only provide financial support but also offer industry knowledge, a network of contacts, and mentorship to the companies they invest in.\nSeries A Funding In the startup ecosystem, Series A funding refers to the first round of institutional investment in a company. Series A funding typically follows an initial seed funding round and is used to fund the company's growth and development.\nDuring the Series A funding round, a company typically raises capital from venture capital firms or other institutional investors in exchange for equity in the company. The amount of capital raised in a Series A funding round can vary widely, but it is generally in the range of $2 million to $15 million.\nThe Series A funding round is a critical stage for startups, as it represents the transition from the early stages of development to a more mature phase of growth. Companies that are successful in raising Series A funding are typically able to demonstrate a clear vision and business plan, as well as traction in the market.Growth hacking\nThis is the use of creative, low-cost, and high-impact marketing strategies to rapidly grow a startup's customer base. Growth hacking often involves experimenting with different channels and tactics to find the most effective and efficient ways to acquire and retain customers.\nDisruptive Technology Disruptive technology is a new invention or idea that changes how a market or industry works. It can replace existing products or services and create new opportunities. Disruptive technologies can affect established industries and companies and change the competitive landscape.\nIn startups, disruptive technologies are often a focus because they can create a lot of value and make the company different from its competitors. Disruptive technologies are found in many industries, such as information technology, healthcare, energy, and transportation.\nExamples of disruptive technologies include personal computers, the internet, mobile devices, and ride-sharing apps. These technologies have changed our lives and work and have had a big effect on the global economy.\nCrowdfunding Crowdfunding is a method for raising money for a project or business by asking for small donations from many people, usually on the internet. Websites like Kickstarter, Indiegogo, and GoFundMe help people and groups start fundraising campaigns and get contributions from ordinary people.\nFor startups, crowdfunding can be a helpful way to get money and see whether people want what they're selling. Lots of startups have used crowdfunding to pay for their ideas and launch new products or businesses.\nThere are different types of crowdfunding. Rewards-based crowdfunding lets people who give money get something in return, like a product or service. Equity-based crowdfunding gives people a share of the company in exchange for their donation.\nNetwork Effects Network effects are when a product or service is more valuable as more people use it. It can be good or bad, depending on the situation.\nIn the startup world, network effects are often important for a company's success or failure. Good network effects can help companies a lot, because they make a cycle where more users make the product or service more valuable, and then more users want to use it.\nExamples of products or services with good network effects are social media, payment systems, and online marketplaces. These types of things get more useful and valuable as more people use them. For example, social media is more valuable as more people join and share things, because users can connect with more kinds of people. Online marketplaces are more valuable as more people join because they have more things to buy.\nBad network effects happen when a product or service is less valuable as more people use it. For example, a road is less valuable as more cars use it because it gets more traffic and takes longer to get anywhere.\nBlue Ocean Strategy Blue ocean strategy is a business strategy that involves creating new markets and finding untapped sources of demand, rather than competing in existing markets with established players. The concept of blue ocean strategy was developed by professors W. Chan Kim and Renée Mauborgne, and is based on the idea that companies can create \u0026quot;blue oceans\u0026quot; of untapped market space by identifying and targeting new or overlooked segments of the market.\nIn the startup ecosystem, blue ocean strategy can be an effective way for companies to differentiate themselves from competitors and achieve sustained growth. By focusing on creating new markets or disrupting existing ones, startups can potentially achieve higher profits and stronger market positions.\nTo implement a blue ocean strategy, a company must first identify and analyze the factors that drive demand in its industry, such as price, features, and convenience. The company can then create a new value proposition that addresses these factors in a unique and innovative way, in order to differentiate itself from competitors and create a new market space.\nLean Startup The lean startup is a business method that emphasizes fast experimentation, constant learning, and customer feedback to quickly validate and improve business ideas. The approach was created by entrepreneur and author Eric Ries and suggests that startups can use agile and data-driven methods to test and refine their business models and avoid expensive mistakes.\nIn the startup world, the lean startup approach is often used by early-stage companies to quickly validate their assumptions about the market and their product or service offering. By using methods such as minimum viable product (MVP) development and customer development, startups can quickly gather feedback and data from customers and use this information to improve their product or service.\nThe lean startup approach suggests that startups should focus on learning and adapting quickly, rather than following a predetermined plan. This can help startups avoid expensive mistakes and allow them to change direction as needed in order to achieve success.\nScalability Scalability means a company can grow and expand its operations without needing more money to do so. This is important for startups because it lets them grow and make money without spending too much.\nThere are a few ways a company can be scalable. For instance, a company can use technology or automation to make its operations more efficient, so it can handle more business without hiring more people. Or, a company can create a business model that's scalable, like a subscription service or an online platform that lets it grow without spending more money.\nInvestors care about scalability because it shows how much a company can grow and make money over a long time. Companies that can scale are more likely to do well even if the economy is bad or the market changes.\nViral Marketing Viral marketing is a way of promoting a product or brand by creating and sharing content that people want to share with others online. The goal is to create a cycle of sharing that generates a lot of exposure and makes people more aware of the company.\nIn the startup world, viral marketing can be a good way to reach a big audience without spending a lot of money. If a startup makes content that's interesting enough to share, people will share it with their friends and followers on social media. This can bring more people to the company's website and increase sales.\nA viral marketing campaign needs certain things to be successful. The message has to be clear and interesting, and it needs to be easy to share with others. It should also appeal to a broad audience.\nAccelerator An accelerator is a program that helps new companies grow by giving them resources, mentorship, and sometimes a small amount of money in exchange for a small part of the company. Accelerators can be very helpful for new companies because they provide access to a network of investors, mentors, and experts who can help them grow.\nIn the world of startups, accelerators are important because they provide many resources for new companies. Some accelerators focus on a particular industry or technology, so they can provide specialized help and resources.\nAccelerator programs usually last for a fixed amount of time, often three to six months. At the end of the program, there is a demo day where the new companies show their progress to investors and experts. If a company does well in an accelerator program, they might be able to get more funding from investors or move on to the next stage of development.\nIncubator An incubator is a program or organization that helps early-stage companies by providing them with resources, mentorship, and sometimes a small amount of funding in exchange for a small equity stake in the company. Incubators are focused on helping companies get started and achieve early success by providing access to mentors, investors, and experts in the industry.\nIncubators are important for early-stage companies because they provide support and resources. They often focus on a specific industry or technology, and give companies access to specialized expertise and resources.\nIncubator programs usually last for a fixed period of time, usually between six and twelve months. Participating companies get workspace, mentorship, and other resources. If a company completes an incubator program successfully, they may be able to get more funding from investors or move on to the next stage of development.\nThe main difference between incubators and accelerators is the stage of development they focus on. Incubators are focused on helping companies get started and achieve early success, while accelerators help companies grow and develop faster. Incubators are more focused on helping companies in the earliest stages of development, while accelerators are for companies that have made some progress already.\nThe length of the program is another difference. Incubators usually last longer, between six and twelve months, while accelerators are shorter, usually between three and six months.\nConvertible Note A convertible note is a type of loan that's often used to fund early-stage startups. It's a short-term loan that can be converted into equity in the company later on. This usually happens when the company raises more money or reaches certain goals.\nStartups use convertible notes when they need money, but aren't ready to do a full round of fundraising yet. With convertible notes, they can raise money without having to figure out how much their company is worth. This is especially helpful in the early stages when the company's value is uncertain.\nConvertible notes usually have a set date when they become due, and may also earn interest. When the note is converted into equity, the interest is added to the amount that's converted. The terms of the conversion, like how much the company is worth when the note is converted, are agreed upon ahead of time and written down in a conversion agreement.\nBootstrapping Bootstrapping means starting and growing a business with very little external funding or support. It's often used by entrepreneurs to get their business started without relying on external funding, like venture capital or loans.\nThere are many strategies for bootstrapping, like starting the business while still working full-time, using personal savings or credit, and reducing expenses to save money. Bootstrapping can also mean making money from selling products or services to grow the business.\nBootstrapping is a challenging way to start a business, but it can be rewarding because it lets entrepreneurs keep control and ownership of their business without taking on debt or giving up equity. However, it's also risky because entrepreneurs must fund the business's growth on their own, which can be really difficult.\nFreemium Freemium is a business model where a basic version of a product or service is offered for free, while extra features or premium content are charged for. Startups often use the freemium model for online products or services, such as software, apps, or content.\nBy offering a basic version for free, companies can get a lot of people interested in their product. When users like the product and depend on it, they might pay for extra features or content.\nThe freemium model helps startups attract and keep users because users can try the product for free. But it can be hard to make money with the freemium model because many users need to pay for extra features in order to make a profit.\nPitch Deck A pitch deck is a presentation that is used by startups to communicate the key elements of their business plan to potential investors. Pitch decks are typically used during pitch meetings or presentations, during which a startup presents its business idea and seeks funding from investors.\nIn the startup ecosystem, pitch decks are a common tool used by entrepreneurs to communicate their vision and value proposition to potential investors. A pitch deck typically includes a high-level overview of the company, including its mission, product or service offering, target market, competitive landscape, and financial projections.\nPitch decks can vary in length and format, but typically include a series of slides that outline the key elements of the company's business plan. The content of a pitch deck may include information on the company's business model, target market, competitive advantage, financial projections, and team.\nPipe In the startup ecosystem, the term \u0026quot;pipe\u0026quot; refers to the pipeline of potential customers or opportunities that a company is pursuing. A company's pipe can include a variety of potential sources of revenue or growth, such as leads, sales, partnerships, or investments.\nIn general, the size and quality of a company's pipe is seen as an indicator of its potential for growth and success. Companies with a strong pipe are typically seen as having a strong foundation for future growth, while those with a weak pipe may face challenges in achieving sustainable growth.\nTo build and maintain a strong pipe, startups often rely on a variety of strategies, such as lead generation, customer acquisition, and business development. By actively building and nurturing its pipe, a company can position itself to take advantage of new opportunities and drive growth.\nLead Generation Lead generation is the process of finding and developing potential customers or clients for a business. It's an important part of business growth for startups, as it helps them find and connect with potential customers or clients and build a list of potential revenue.\nLead generation can involve many different methods, like targeted marketing, content creation, social media, event sponsorship, and online ads. The goal of lead generation is to create and grow relationships with possible customers or clients and turn them into paying customers.\nLead generation is a continuous process that requires ongoing effort and investment. For startups to succeed, they have to be proactive in finding and following up with leads and have systems and processes in place to manage and grow those relationships over time.\nRunway In the world of startups, \u0026quot;runway\u0026quot; means the amount of time a company can operate before it runs out of money. Runway is important to startups because it shows how long they can keep going and growing without more money.\nThe length of a company's runway depends on two things: its burn rate, which is how much money it spends each month, and its cash balance. If a company spends a lot of money each month and has very little cash, it will have a short runway. If a company spends less money each month and has more cash, it will have a longer runway.\nStartups want to have a long runway so they can reach important goals, like making money or getting more investment. Startups with a long runway can handle hard times better and change with the market more easily.\nSweat Equity Sweat equity means that people add value to a company by working hard, instead of investing money. In startups, sweat equity is a way for founders and early employees to help the company grow, in exchange for getting a share of the company. This is especially useful when the company doesn't have money to pay people. Sweat equity can help to build and grow the company, without paying people in cash.\nTraction Traction means the progress and momentum that a company has made in terms of acquiring customers, increasing revenue, or other key indicators of success. Traction is important for startups because it shows how well the company has entered the market and grown in a steady way.\nTraction can be measured in many ways, depending on the specific goals and metrics that the company decides to use. Common ways to measure traction include the number of users or customers, the speed of customer acquisition, revenue growth, and other indicators of market demand or adoption.\nStartups that achieve traction are seen as having a stronger foundation for growth and are more likely to attract investment and become profitable. However, achieving traction is hard and competitive, and startups must work hard to stand out and build a strong customer base.\nIdeation Ideation means creating and developing new ideas for products or businesses. It is an important part of starting a business, as it helps to come up with new and creative ideas that can be turned into successful products or services.\nIdeation can be done in many ways, for example brainstorming, problem-solving, customer research, and market analysis. The goal of ideation is to find opportunities for innovation and to create ideas that can be developed into successful products or services that meet the needs of a specific market or customer base.\nZero-Sum Game A zero-sum game is when one person's gain is exactly balanced by another person's loss, resulting in a total gain and loss of zero.\nIn business, zero-sum games can happen when one company's gains come at the expense of another company, like in a price war or competition for market share. This can lead to a zero-sum outcome, where one company's gain is balanced by the other company's loss.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/beyond-code/commonly-used-terms-startup-ecosystem/","section":"beyond-code","tags":["business","start-up"],"title":"Commonly Used Terms in The Startup Ecosystem"},{"body":"","link":"https://blog.sksoumik.com/categories/non-technical/","section":"categories","tags":null,"title":"non-technical"},{"body":"","link":"https://blog.sksoumik.com/tags/start-up/","section":"tags","tags":null,"title":"start-up"},{"body":"","link":"https://blog.sksoumik.com/tags/statistics/","section":"tags","tags":null,"title":"statistics"},{"body":"Why do we scale features? For machine learning, every dataset does not require feature scaling, and it is only needed when features have different ranges.\nFor example, consider a data set containing two features, age(x1) and income(x2), where age ranges from 0–100, while income ranges from 0–20,000 and higher. Income is about 1,000 times larger than age and ranges from 20,000–500,000. So, these two features are in very different ranges. When we do further analysis, like multivariate linear regression, the attributed income will influence the result more due to its larger value. But this doesn’t necessarily mean it is more important as a predictor.\nBecause different features do not have similar ranges of values, gradients may take a long time, oscillate back and forth, and take a long time before they can finally find their way to the global/local minimum. To overcome the model learning problem, we normalize the data. We ensure that the different features take on similar ranges of values so that gradient descents can converge more quickly.\nWhen to use Normalization? Normalization typically means rescaling the values into a range of [0,1].\nNormalization is an excellent technique to use when you do not know the distribution of your data or when you know the distribution is not Gaussian (a bell curve). Normalization is useful when your data has varying scales, and the algorithm you are using does not make assumptions about the distribution of your data, such as k-nearest neighbors and artificial neural networks.\nWhen to use Standardization? Standardization: typically means rescales data to have a mean of 0 and a standard deviation of 1 (unit variance).\nStandardization assumes that your data has a Gaussian (bell curve) distribution. This does not strictly have to be true, but the technique is more effective if your attribute distribution is Gaussian. Standardization is useful when your data has varying scales, and your algorithm makes assumptions about your data having a Gaussian distribution, such as linear regression, logistic regression, and linear discriminant analysis.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/data-normalization-vs-standardization-machine-learning/","section":"artificial-intelligence","tags":["data science","statistics","machine learning"],"title":"Understanding the Role of Data Normalization and Standardization in Machine Learning"},{"body":"When we run web crawlers, sometimes we get blocked by the target site. Sometimes we get reCAPTCHA to solve, and crawling gets interrupted. We can rotate the IP address with each request to avoid these issues, which solves the IP blocking and reCAPTCHA issues.\nThis blog is the written version of the video content I published on YouTube. If you prefer watching videos than reading blogs, then you can watch the video.\nWe will use a Tor proxy to rotate the IP address with each HTTP request. First, let's install the Tor browser. Open the terminal and use the following commands to install the Tor browser on your machine:\n1sudo add-apt-repository ppa:micahflee/ppa 2sudo apt install torbrowser-launcher Now, you should have a torrc file in your /etc/tor/ directory.\nEdit torrc file in your /etc/tor/ directory open the torrc file using $ sudo nano torrc and uncomment the following lines (usually these are commented out):\n1ControlPort 9051 2HashedControlPassword 16:2D99FRCE35858C6F608DB3122A6C8DA4C35BE5E105B9B54A7E438B122F 3CookieAuthentication 1 There is a HashedControlPassword in your torrc file, we will replace this password with a new password created by you. Use the following command to create a new password. Open up your terminal and create a new password using the following command.\n1tor --hash-password \u0026lt;password key\u0026gt; For example,\n1tor --hash-password mypass This will create a password for the key mypass and display the password on your terminal. Note the key and password both. We will use both later.\nNow, replace the HashedControlPassword in your torrc file, which is located in /etc/tor/ directory. You can use nano or any other editor. Save the torrc file.\nNow, we will use the mypass keyword to renew connections with each request. First, you have to install the stem and request library.\n1pip install stem 2pip install requests Now, create a new python file and use the following code to change your IP address:\n1from stem import Signal 2from stem.control import Controller 3import requests 4 5 6def get_tor_session(): 7 # initialize a requests Session 8 session = requests.Session() 9 # this requires a running Tor service in your machine and listening on port 9050 (by default) 10 session.proxies = { 11 \u0026#34;http\u0026#34;: \u0026#34;socks5://127.0.0.1:9050\u0026#34;, 12 \u0026#34;https\u0026#34;: \u0026#34;socks5://127.0.0.1:9050\u0026#34;, 13 } 14 return session 15 16 17def renew_connection(): 18 with Controller.from_port(port=9051) as controller: 19 controller.authenticate(password=\u0026#34;mypass\u0026#34;) 20 controller.signal(Signal.NEWNYM) See, how we have used mypasskeyword in the renew_connection method.\nNow, let's use the tor session to send http request to some URLs.\n1headers = { 2 \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11\u0026#34; 3} 4 5 6def send_request(url_list): 7 for url in url_list: 8 try: 9 # renew the connection 10 renew_connection() 11 # create a new tor session 12 session = get_tor_session() 13 html_content = session.get(url, headers=headers).text 14 print( \u0026#34;IP rotated to:\u0026#34;, 15 session.get(\u0026#34;https://ident.me\u0026#34;, headers=headers).text) 16 17 except Exception as e: 18 print(e) 19 pass 20 21if __name__ == \u0026#34;__main__\u0026#34;: 22 # IP address before IP rotation 23 print(\u0026#34;Your Public IP:\u0026#34;, requests.get(\u0026#34;https://ident.me\u0026#34;).text) 24 urls = [ 25 \u0026#34;https://www.google.com\u0026#34;, 26 \u0026#34;https://www.facebook.com\u0026#34;, 27 \u0026#34;https://www.youtube.com\u0026#34;, 28 \u0026#34;https://www.amazon.com\u0026#34;, 29 ] * 10 30 31 send_request(urls) We are using the https://ident.me site to print the IP address with each request. You will see different IP address with each print statement execution.\nFollowing this procedure, the program might become slow. So it's better to use multiprocessing or multithreading to make the process faster. You can do the following to do multiprocessing.\n1from multiprocessing import Pool 2 3if __name__ == \u0026#34;__main__\u0026#34;: 4 # IP address before IP rotation 5 print(\u0026#34;Your Public IP:\u0026#34;, requests.get(\u0026#34;https://ident.me\u0026#34;).text) 6 7 urls = [ 8 \u0026#34;https://www.google.com\u0026#34;, 9 \u0026#34;https://www.facebook.com\u0026#34;, 10 \u0026#34;https://www.youtube.com\u0026#34;, 11 \u0026#34;https://www.amazon.com\u0026#34;, 12 ] * 10 13 14 # send requests in parallel using multiprocessing 15 with Pool(processes=20) as pool: 16 pool.map(send_request, [urls[i : i + 10] for i in range(0, len(urls), 10)]) 17 pool.close() 18 pool.join() Code can be found in this GitHub repository : https://github.com/sksoumik/rotate_IP\nThanks for the read.\n","link":"https://blog.sksoumik.com/software-engineering/rotate-ip-with-every-http-request-bypass-recaptcha-tor-proxy/","section":"software-engineering","tags":["linux","proxy","web scraping","project-tutorial"],"title":"Bypass reCAPTCHA And Prevent IP Blocking Using Tor Proxy"},{"body":"","link":"https://blog.sksoumik.com/tags/linux/","section":"tags","tags":null,"title":"linux"},{"body":"","link":"https://blog.sksoumik.com/categories/operating-system/","section":"categories","tags":null,"title":"operating system"},{"body":"","link":"https://blog.sksoumik.com/tags/proxy/","section":"tags","tags":null,"title":"proxy"},{"body":"","link":"https://blog.sksoumik.com/tags/web-scraping/","section":"tags","tags":null,"title":"web scraping"},{"body":"Best way to use extensions is to set keyword shortcuts in chrome://extensions/shortcuts URL. It helps to quickly activate the extension just by using the predefined keyword shortcuts.\nFocus To-Do: Pomodoro Timer \u0026amp; To Do List Focus To-Do combines Pomodoro Timer with Task Management, it is an app that will motivate you to stay focused and get things done.\nIt brings Pomodoro Technique and To Do List into one place, you can capture and organize tasks into your todo lists, start focus timer and focus on work \u0026amp; study, set reminders for important tasks and errands, check the time spent at work.\nChrome Store Link\nVoice Search Search by speaking. Voice Search uses the power of speech recognition to search the web! Instead of typing, use voice input to quickly and easily search for the things you care about. This works on any web page. All you have to do is activate the extension, and say something, and that will open the google search result for whatever you said on a different tab.\nChrome Store Link\nGoogle Keep Chrome Extension I use it to save any important text/quote that I find helpful while surfing the internet so that I can revise it later. All I have to do is mark any text from any web page and right-click on the mouse, showing a save option. Text gets saved in Google Keep account like a card.\nChrome Store Link\nGoogle Dictionary Double-click any word to view its definition in a small pop-up bubble. View the complete definition of any word or phrase using the toolbar dictionary. Store a history of words you've looked up, so you can practice them later.\nChrome Store Link\nFree Auto Text Expander Create custom keyboard shortcuts to expand and replace text as you type!\nWe can set shortcuts/acronyms for long texts, which is very useful for filling up forms where you probably type the same things repeatedly. For example, I set SKS for my full name \u0026quot;Sadman Kabir Soumik.\u0026quot; So, after activating the extension, if I type SKS, it automatically expands the full name.\nChrome Store Link\nScreen Shader | Smart Screen Tinting Computer displays produce bright blue light, which can strain eyes late at night and interfere with the sleeping cycle. Screen Shader is designed to tint computer display a \u0026quot;cozy\u0026quot; orange color to reduce eye strain and eye fatigue and restore day/night balance while providing a wide variety of settings for everyone's tastes!\nChrome Store Link\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/beyond-code/top_chrome_extensions_for_productivity/","section":"beyond-code","tags":["productivity","life hack"],"title":"Chrome Extensions That I Use for Productivity"},{"body":"","link":"https://blog.sksoumik.com/tags/image-segmentation/","section":"tags","tags":null,"title":"image segmentation"},{"body":"","link":"https://blog.sksoumik.com/tags/life-hack/","section":"tags","tags":null,"title":"life hack"},{"body":"In computer vision, image segmentation refers to the process of dividing an image into distinct regions or segments, each corresponding to a different object or background. There are two main approaches to image segmentation: one-stage and two-stage.\nOne-stage image segmentation methods aim to directly predict a segmentation mask for the entire image in a single pass. These methods are typically faster and more efficient than two-stage methods, but they may be less accurate and less flexible.\nTwo-stage image segmentation methods, on the other hand, use a two-step process to generate a segmentation mask. In the first step, these methods generate a set of candidate object proposals, which are potential locations and sizes of objects in the image. In the second step, these proposals are refined and combined to generate the final segmentation mask. Two-stage methods are typically more accurate and flexible than one-stage methods, but they may be slower and more computationally expensive.\nOverall, the choice between one-stage and two-stage image segmentation methods depends on the specific application and the trade-offs between speed, accuracy, and flexibility.\nTwo-Stage Segmentation Examples Some examples of two-stage image segmentation methods include:\nRegion proposal networks (RPNs): RPNs are a type of object detection method that can be used for image segmentation. RPNs use a convolutional neural network to generate a set of candidate object proposals, which are potential locations and sizes of objects in the image. The proposals are then passed to a separate network for classification and refinement, which produces the final segmentation mask. Selective search: Selective search is a bottom-up approach to object proposal generation, which can be used in conjunction with a convolutional neural network for image segmentation. Selective search uses a combination of color, texture, and shape information to generate a set of candidate object proposals, which are then passed to the convolutional neural network for classification and refinement. EdgeBoxes: EdgeBoxes is another bottom-up approach to object proposal generation, which can be used for image segmentation. EdgeBoxes uses edge information in the image to generate a set of candidate object proposals, which are then passed to a convolutional neural network for classification and refinement. Models Faster R-CNN: Faster R-CNN is a popular two-stage object detection model that can be used for image segmentation. Faster R-CNN uses a region proposal network (RPN) to generate candidate object proposals, which are then passed to a separate network for classification and refinement. The final bounding boxes can be used to generate the segmentation mask. Mask R-CNN: Mask R-CNN is a two-stage image segmentation model based on the Faster R-CNN object detection model. Mask R-CNN adds a branch to the Faster R-CNN network to predict segmentation masks for each detected object. Cascade R-CNN: Cascade R-CNN is a two-stage object detection model that improves on the Faster R-CNN model by adding a cascade of classifiers to the region proposal network. This allows the model to refine the object proposals and improve the accuracy of the final segmentation masks. YOLACT: YOLACT is a two-stage image segmentation model that uses a single shot detector (SSD) network to generate object proposals, and then refines the proposals using a separate network to generate the final segmentation masks. One-stage Segmentation Examples Some examples of one-stage image segmentation methods include:\nFully convolutional networks (FCNs): FCNs are a type of deep neural network that can take an image of arbitrary size as input and produce a corresponding segmentation mask. FCNs use a series of convolutional and pooling layers to extract features from the input image, and then use a deconvolutional layer to upsample the features to the size of the input image and generate the final segmentation mask. Single shot detectors (SSDs): SSDs are a type of object detection method that can be used for image segmentation. SSDs use a convolutional neural network to simultaneously predict object classes and locations in the image, using a single pass through the network. The predicted bounding boxes can then be used to generate the segmentation mask. Region-based convolutional neural networks (RCNNs): RCNNs are another type of object detection method that can be used for image segmentation. RCNNs use a two-stage process, where the first stage generates object proposals and the second stage uses a convolutional neural network to refine the proposals and generate the final segmentation mask. However, unlike two-stage segmentation methods, the proposal generation and refinement stages are both performed using a single network, rather than two separate networks. Models Mask R-CNN: Mask R-CNN is a popular one-stage image segmentation model based on the Faster R-CNN object detection model. Mask R-CNN adds a branch to the Faster R-CNN network to predict segmentation masks for each detected object. U-Net: U-Net is a one-stage image segmentation model based on a fully convolutional network (FCN) architecture. U-Net uses a series of convolutional and deconvolutional layers to extract features from the input image and generate the final segmentation mask. DeepLab: DeepLab is another one-stage image segmentation model based on an FCN architecture. DeepLab uses a combination of atrous convolutions and spatial pyramid pooling to extract features from the input image and generate the final segmentation mask. PSPNet: PSPNet is a one-stage image segmentation model based on the ResNet-101 convolutional neural network. PSPNet uses spatial pyramid pooling to extract features from the input image and generate the final segmentation mask. Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/one-stage-two-stage-segmentation-difference/","section":"artificial-intelligence","tags":["image segmentation","machine learning","computer vision","data science"],"title":"One-Stage vs Two-Stage Instance Segmentation"},{"body":"","link":"https://blog.sksoumik.com/tags/productivity/","section":"tags","tags":null,"title":"productivity"},{"body":"","link":"https://blog.sksoumik.com/tags/automation/","section":"tags","tags":null,"title":"automation"},{"body":"","link":"https://blog.sksoumik.com/tags/google-cloud/","section":"tags","tags":null,"title":"google cloud"},{"body":"Suppose you have a script in the Google Cloud’s VM Instance that needs to run every day/week/month at a particular time ( e.g., a data pipeline for Machine Learning model training, data crawling, etc.). In that case, this article will guide you through automating the task.\nDifferent ways you can automate a script on GCP Your script/program can be in any language, but let’s assume we are trying to automate a Python script for simplicity.\nLet’s say you are working on Google Compute Engine’s VM instance. You have a Python script main.py that does some specific task ( e.g., scrape data from multiple sites, train Machine Learning model, etc.). You need to do this same task (running the script) every Friday at 12:00 AM; then, you have a couple of options to automate this process.\nUse cloud functions with pub/sub. Add a startup-script in your VM instance to run the program automatically. You can consider the first option if your job takes less than 540 seconds. Otherwise, it’s ideal to go with the second approach. The first option won’t work for long-running processes because the Google Cloud Functions can run a maximum of 940 seconds¹.\nBut what is a ‘startup-script’? A startup script is a file that performs tasks during the startup process of a virtual machine (VM) instance. Startup scripts can apply to all VMs in a project or to a single VM⁴.\nConfigure the VM Instance and Environment If you try to add a startup-script to your VM instance², your startup-script will run in the root user mode. When you connect your VM instance remotely with an SSH connection to your local machine, you log in as a different user from the root mode. You can enter the root user mode by the following command:\n1$ sudo su - This command will take you to the root user mode. You will not find your coding resources in root mode, which you probably worked on while connecting the VM instance via SSH to your local machine. So, I suggest maintaining a git repo to maintain your code and cloning the git repo in the root user. You should also install program dependencies into the root user. For example, if your program runs on Anaconda Virtual Environment, then install Anaconda in the root user. Ensure you can run your program correctly by logging into the root user mode. Let’s say you can perfectly run your program by the following command in your virtual environment:\n1$ python main.py There might be different python interpreters existing in your VM. Just make sure which python interpreter you are using to run your program, you can run the following command in the terminal to see which interpreter you are using. If you are working inside a virtual environment, then activate it, and run the following command.\n1$ which python This will give your python interpreter path, like usr/bin/python3 .\nConfigure the The startup-script on VM Instance Settings To attach a startup-script in your instance, go to the Compute Engine \u0026gt; VM Instances page. Then click on your target VM Instance. Now, Edit the instance.\nClick on Edit. after that, scroll down the page, and you will find a section called ‘Metadata’. There, under the Automation section, you can add your startup-script .\nFor example: let’s say our driver code exists in the /root/data_pipeline/src/main.py file. So, to run the program automatically when VM starts, we can write the following startup-script .\n1#! /bin/bash 2 3/usr/bin/python3 /root/data_pipeline/src/main.py We want to run the program to keep running without any kind of interruption, even if the configuration changes in some other VMs and the main GCP project. To run the program uninterruptedly, we should enable shielded-learn-integrity-policy policy³. To do that, we can add the following command before calling the main.py script.\n1#! /bin/bash 2gcloud compute instances update \u0026lt;instance-name\u0026gt; --zone \u0026lt;instance-zone-name\u0026gt; --shielded-learn-integrity-policy 3 4/usr/bin/python3 /root/data_pipeline/src/main.py When the main.py program execution gets completed, we want our VM instance gets stopped automatically. To do that, we can add the following command at the end of the startup-script :\n1#! /bin/bash 2 3gcloud compute instances update \u0026lt;your-instance-name\u0026gt; --zone \u0026lt;instance-zone-name\u0026gt; --shielded-learn-integrity-policy 4 5# run the main script that you want to run 6/usr/bin/python3 /root/data_pipeline/src/main.py 7 8# turn of the instance 9gcloud compute instances stop \u0026lt;your-instance-name\u0026gt; --zone \u0026lt;instance-zone-name\u0026gt; You can also add other commands in thestartup-script as per your requirements. For example:\n1#! /bin/bash 2sudo service tor restart 3 4gcloud compute instances update \u0026lt;your-instance-name\u0026gt; --zone \u0026lt;instance-zone-name\u0026gt; --shielded-learn-integrity-policy 5 6# set the file open limit 7ulimit -n 100000 8 9# use the python interpreter to run the main script 10/usr/bin/python3 /root/data_pipeline/src/main.py 11 12# stop the instance automatically after the program finishes its execution 13gcloud compute instances stop \u0026lt;your-instance-name\u0026gt; --zone \u0026lt;instance-zone-name\u0026gt; The above script makes our program to gets executed automatically when the VM instance gets started and stops the VM instance after the program execution gets completed.\nNow, we need to schedule the instance to automatically starts the VM instance.\nSchedule the VM instance Go to the Navigation menu \u0026gt; Compute Engine \u0026gt; VM Instances page. There is a section called INSTANCE SCHEDULES .\nGo to that tab. Now, you will find a section in the top bar called CREATE SCHEDULE\nIf you click on the Create Schedule, you will see a page like the below:\nGive any name you want. The region area must match the VM instance’s region. Define a Start time and Frequency (like daily / weekly / monthly, etc.). You don’t need to define any Stop time, as we already have a command to stop the instance automatically after our main.py program ends its execution in the startup-script .\nYou can also use CRON expressions to define the start time for instance. Finally, submit the page. This will create a scheduler page. Go to that page, and you will find options to add instances in it.\nJust add your target instance to it, and you’re done.\nThis will allow your instance to automatically start at your defined start time on the scheduled page, call the main.py file from the startup-script , after the program finishes its execution, the VM instance will be automatically turned off.\nReferences Author: Sadman Kabir Soumik\n[1]. https://cloud.google.com/functions/quotas\n[2]. https://cloud.google.com/compute/docs/instances/startup-scripts/linux\n[3]. https://cloud.google.com/compute/shielded-vm/docs/integrity-monitoring#updating-baseline\n[4]. https://cloud.google.com/compute/docs/instances/startup-scripts/\n","link":"https://blog.sksoumik.com/cloud-computing/automate-gcp-compute-engine-vm-instance/","section":"cloud-computing","tags":["cloud","google cloud","automation"],"title":"How to Automate and Schedule GCP VM Instance"},{"body":"There are several key differences between using machine learning for research and using it for production.\nOne of the main differences is the focus of the work. Machine learning for research typically focuses on exploring new ideas and techniques, and on advancing the state of the art in the field. In contrast, machine learning for production focuses on building practical, real-world applications that can deliver value to organizations and individuals.\nAnother key difference is the level of complexity and scale involved. Machine learning for research often involves working with small, carefully curated datasets, and may involve the development of complex, highly customized algorithms and models. In contrast, machine learning for production typically involves working with large, messy, real-world datasets, and may require the use of more robust, general-purpose algorithms and models that can handle this complexity and variability.\nA third key difference is the level of performance and accuracy required. Machine learning for research often involves the pursuit of theoretical performance limits, and may involve the use of highly accurate but computationally intensive algorithms. In contrast, machine learning for production often involves the need to balance accuracy with efficiency and cost, and may require the use of algorithms and models that are less accurate but more scalable and efficient.\nAnother difference is the level of experimentation and exploration involved. Machine learning for research often involves a high degree of experimentation and exploration, and may involve the use of novel and untested techniques and approaches. In contrast, machine learning for production often involves more focused and directed work, and may require the use of more established and proven techniques and methods.\nThings to consider when developing machine learning models for production When it comes to developing machine learning models for production, there are several key considerations that need to be taken into account. These considerations are important not only for the success of the project, but also for ensuring that the model is able to operate effectively and efficiently in a real-world environment.\nFirst and foremost, it is important to carefully plan and design the machine learning model. This means carefully selecting the right algorithms and techniques for the task at hand, as well as ensuring that the model is able to handle the complexity and variability of real-world data. It is also important to carefully evaluate the performance of the model, using metrics such as accuracy, precision, and recall to assess its effectiveness.\nAnother key consideration when developing machine learning models for production is the need for robustness and reliability. This means that the model must be able to handle a wide range of input data, including edge cases and outliers, without breaking or producing incorrect results. It is also important to ensure that the model is able to handle changes in the data over time, as well as any unexpected events or situations that may arise.\nAdditionally, it is important to consider the computational resources required to run the machine learning model in production. This means carefully selecting the right hardware and software infrastructure, as well as ensuring that the model is able to scale and adapt to changing workloads and requirements. It is also important to carefully monitor the performance of the model in production, and make any necessary adjustments to improve its efficiency and effectiveness.\nFinally, it is crucial to consider the ethical and legal implications of using machine learning models in production. This means ensuring that the model is not biased or discriminatory, and that it respects the privacy and security of individuals. It is also important to carefully evaluate the potential risks and liabilities associated with using machine learning models, and to put in place appropriate safeguards and controls to mitigate these risks.\nEvaluate model performance | Research vs Production One of the key differences between evaluating machine learning models in research and production is the focus of the evaluation. In research, machine learning models are typically evaluated in terms of their ability to advance the state of the art in the field, and to push the boundaries of what is possible with machine learning. In contrast, in production, machine learning models are typically evaluated in terms of their ability to deliver value and to solve real-world problems.\nAnother key difference is the metrics and benchmarks used to evaluate the models. In research, machine learning models are often evaluated using specialized metrics and benchmarks that are designed to measure their performance on specific tasks or datasets. These metrics may be theoretical or abstract, and may not always reflect the real-world performance of the model. In production, machine learning models are typically evaluated using more practical and relevant metrics, such as accuracy, precision, and recall, that are designed to measure their performance on real-world data and tasks.\nA third key difference is the testing frameworks and environments used to evaluate the models. In research, machine learning models are often evaluated using custom-built testing frameworks and environments, which may be specifically designed to test the model on specific tasks or datasets. In production, machine learning models are typically evaluated using more general-purpose testing frameworks and environments, which may be integrated into existing systems and processes.\nIn production, once the evaluation metrics and data have been defined, the next step is to implement a process for monitoring and measuring the model's performance in production. This may involve setting up automated processes to collect and analyze data on the model's performance, and to trigger alerts or notifications if the performance falls below a certain threshold. It is also important to regularly review the performance of the model, and to take action to improve its performance if necessary.\nIn addition to monitoring and measuring the model's performance, it is also important to evaluate its accuracy and reliability. This may involve conducting regular tests and experiments to assess the model's performance on a variety of data and scenarios, and to identify any potential issues or problems. It is also important to carefully evaluate the model's ability to handle edge cases and outliers, and to ensure that it is able to operate effectively and efficiently in a real-world environment.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/machine_learning_research_vs_production/","section":"artificial-intelligence","tags":["machine learning","deep learning","data science"],"title":"Machine Learning Practices - Research vs Production"},{"body":"PyTorch and Keras are both open-source deep learning frameworks, but they have some significant differences. PyTorch is a low-level framework that allows you to define your own computation graphs, while Keras is a high-level framework that provides a pre-defined set of layers and routines for building deep learning models. This means that PyTorch offers more flexibility and customization, while Keras is easier to use and more accessible to beginners.\nChoosing Framework - PyTorch vs. TF-Keras When deciding between PyTorch and TensorFlow (or Keras), it's important to consider the specific goals and requirements of your project. PyTorch is a good choice for research and development, as it allows for high flexibility and customization, and it integrates well with the Python ecosystem. It also provides a more intuitive interface for working with dynamic graphs, which can be useful for working with complex, unstructured data.\nOn the other hand, TensorFlow (with or without Keras) is a good choice for production, as it provides a more stable and efficient platform for deploying machine learning models. TensorFlow also has a larger community and more comprehensive support for deploying models on different platforms, such as mobile and web. Additionally, TensorFlow provides a range of pre-trained models and tools for working with large-scale datasets, which can be useful for applications that require high performance and scalability.\nDifferent Ways to Write Machine Learning Models Using Tensorflow-Keras API There are several ways to define a model in the TensorFlow Keras API. Here are some of the most common ways:\nSequential model This is the most common way to define a model in Keras. A Sequential model is a linear stack of layers, where you can use the add() method to add layers to the model. For example:\n1model = tf.keras.models.Sequential() 2model.add(tf.keras.layers.Dense(units=64, activation=\u0026#39;relu\u0026#39;, input_shape=(32,))) 3model.add(tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;)) Functional API The Keras functional API is a way to create models that is more flexible than the Sequential model. It allows you to create models that have multiple inputs and outputs, and it also lets you create models that share layers. For example:\n1inputs = tf.keras.Input(shape=(32,)) 2x = tf.keras.layers.Dense(units=64, activation=\u0026#39;relu\u0026#39;)(inputs) 3outputs = tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;)(x) 4 5model = tf.keras.Model(inputs=inputs, outputs=outputs) Model subclassing You can also define a model using the Keras subclassing API, which allows you to define a model by creating a subclass of the Model class and defining the layers in the __init__() method and the forward pass in the call() method. For example:\n1class MyModel(tf.keras.Model): 2 def __init__(self): 3 super(MyModel, self).__init__() 4 self.dense1 = tf.keras.layers.Dense(units=64, activation=\u0026#39;relu\u0026#39;) 5 self.dense2 = tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;) 6 7 def call(self, inputs): 8 x = self.dense1(inputs) 9 return self.dense2(x) 10 11model = MyModel() These are just some of the ways you can define a model in the TensorFlow Keras API. You may also want to check out the official TensorFlow Keras documentation for more information: https://www.tensorflow.org/api_docs/python/tf/keras/Model.\nDifferent Ways to Write Machine Learning Model Using PyTorch There are several ways to define a model in PyTorch, including:\nnn.Module This is the most common way to define a model in PyTorch. To define a model using nn.Module, you create a class that subclasses nn.Module and define the layers in the __init__() method and the forward pass in the forward() method. Here's an example:\n1class MyModel(nn.Module): 2 def __init__(self): 3 super(MyModel, self).__init__() 4 self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3) 5 self.fc1 = nn.Linear(in_features=24 * 24 * 32, out_features=10) 6 7 def forward(self, x): 8 x = self.conv1(x) 9 x = x.view(-1, 24 * 24 * 32) # flatten the tensor 10 return self.fc1(x) 11 12model = MyModel() nn.Sequential This is a way to define a model by creating a nn.Sequential object and adding layers to it using the add_module() method. For example:\n1model = nn.Sequential( 2 nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3), 3 nn.Linear(in_features=24 * 24 * 32, out_features=10) 4) Functional API PyTorch also has a functional API that is similar to the Keras functional API. This allows you to define a model using functions like nn.conv2d() and nn.linear(), and then use torch.nn.utils.functinoal.make_model() to create a model from the functions. Here's an example:\n1def conv_fn(x): 2 return nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3)(x) 3 4def fc_fn(x): 5 x = x.view(-1, 24 * 24 * 32) # flatten the tensor 6 return nn.Linear(in_features=24 * 24 * 32, out_features=10)(x) 7 8model = torch.nn.utils.function.make_model(conv_fn, fc_fn) These are just some of the ways you can define a model in PyTorch. You may also want to check out the official PyTorch documentation for more information: https://pytorch.org/docs/stable/nn.html.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/writing-model-keras-vs-pytorch/","section":"artificial-intelligence","tags":["machine learning","deep learning","data science"],"title":"Writing Machine Learning Model - PyTorch vs. TF-Keras"},{"body":" Author: Sadman Kabir Soumik\nGPT-3, or Generative Pretrained Transformer 3, is a state-of-the-art language model developed by OpenAI. It has been trained on a massive amount of text data, including books, articles, and websites, to generate coherent and relevant text based on a given context.\nGPT-3 is a transformer-based model, which means that it uses a type of neural network architecture called a transformer to process the input text. This allows the model to capture long-range dependencies and generate text that is more coherent and human-like than previous models.\nOne of the most impressive features of GPT-3 is its size and capacity. It has 175 billion parameters, making it the largest language model ever created. This allows it to generate text that is more realistic and sophisticated than previous models.\nGPT-3 has many potential applications, including natural language processing tasks such as language translation, text summarization, and question answering. It can also be fine-tuned for specific tasks, such as generating text in a specific style or format.\nOne of the most exciting potential applications of GPT-3 is in the field of chatbots and virtual assistants. With its ability to generate human-like text, GPT-3 could be used to create more advanced and realistic chatbots that can have natural conversations with users.\nGPT-3 also has the potential to be used in creative applications, such as poetry generation and storytelling. It could even be used to help automate the writing of articles or other types of content.\nOverall, GPT-3 is a major advancement in the field of natural language processing and has the potential to revolutionize how we interact with computers and generate text. Its large size and advanced capabilities make it a powerful tool for generating human-like text.\nAs with any AI technology, there are also potential concerns and challenges associated with GPT-3. One of the main challenges is the potential for the model to generate biased or offensive text if it is trained on biased data. This highlights the importance of ensuring that the data used to train GPT-3 is diverse and representative of different perspectives and experiences.\nAnother challenge is the potential for GPT-3 to be used for malicious purposes, such as generating fake news or impersonating individuals online. This underscores the need for careful oversight and regulation of the use of GPT-3 and other advanced AI technologies.\nDespite these challenges, the potential benefits of GPT-3 are significant and exciting. As the technology continues to develop and improve, it is likely to have far-reaching implications for natural language processing and the way we interact with computers. It is an exciting development that will likely have a major impact on the future of language technology.\nArchitecture of GPT-3 GPT-3 is a transformer-based model, which means that it uses a type of neural network architecture called a transformer to process the input text. This architecture allows the model to capture long-range dependencies and generate more coherent and human-like text than previous models.\nThe transformer architecture consists of two main components: the encoder and the decoder. The encoder takes in the input text and generates a representation of the input called an embedding. This embedding is then passed to the decoder, which generates the output text.\nThe encoder in GPT-3 is made up of a stack of multiple transformer blocks. Each transformer block consists of a self-attention layer, a feed-forward layer, and a normalization layer. The self-attention layer allows the model to attend to different parts of the input text simultaneously, while the feed-forward layer allows the model to process the input and generate the embedding.\nThe decoder in GPT-3 is also made up of a stack of transformer blocks, which are similar to the ones in the encoder. The decoder uses the embedding generated by the encoder to generate the output text. It does this by predicting the next word in the sequence based on the previous words in the sequence.\nOne of the key features of GPT-3 is its large number of parameters. It has 175 billion parameters, making it the largest language model ever created. These parameters are essentially weights that determine how the model processes the input data and generates the output text.\nThe large number of parameters in GPT-3 allows the model to generate more realistic and sophisticated text than previous models. It also allows the model to be fine-tuned for specific tasks and to generate text in different styles and formats.\nThe training data for GPT-3 consists of a wide range of text, including books, articles, and websites. This allows the model to learn from a diverse set of sources and generate text that is coherent and relevant to a given context.\nOverall, the large number of parameters and the diverse training data used in GPT-3 are key factors that contribute to the model's ability to generate human-like text. These factors make GPT-3 a powerful tool for natural language processing tasks and other applications.\nKey Points About GPT-3 GPT-3 is a groundbreaking language model with 175 billion parameters, making it the largest of its kind to date. It has been trained on an impressive 45TB of text data, which enables it to generate fluent and human-like outputs. The model itself does not possess inherent knowledge and is not designed for storing or retrieving facts. Instead, it excels at predicting the next word or words in a given sequence.\nOne of the key advantages of GPT-3 is its \u0026quot;task-agnostic\u0026quot; nature, which means that you don't need task-specific datasets to accomplish a given task. However, access to the model is limited to those with an API key, as it has \u0026quot;closed-API\u0026quot; access.\nIt's worth noting that GPT-3 is optimized for English language tasks, and its outputs tend to degrade when generating long text. Additionally, the outputs can sometimes be biased or abusive, and there are known contamination issues with the benchmark experiments. Overall, while GPT-3 is an impressive model, it is not without its limitations.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/a_brief_introduction_on_openai_gpt_3/","section":"artificial-intelligence","tags":["machine learning","NLP","data science"],"title":"GPT-3 by OpenAI - The Largest and Most Advanced Language Model Ever Created"},{"body":"What is Vanishing Gradient Problem Neural networks are trained using stochastic gradient descent. This involves first calculating the prediction error made by the model and using the error to estimate a gradient used to update each weight in the network so that less error is made next time. This error gradient is propagated backward through the network from the output layer to the input layer.\nAs the backpropagation algorithm advances downwards (or backward) from the output layer towards the input layer, the gradients often get smaller and smaller and approach zero, eventually leaving the weights of the initial or lower layers nearly unchanged. As a result, the gradient descent never converges to the optimum, known as the vanishing gradients problem.\nWhy does it happen? Certain activation functions, like the sigmoid function, squishes the sample input space into a small input space between 0 and 1. Therefore, a significant change in the input of the sigmoid function will cause a slight change in the output. Hence, the derivative becomes small.\nHowever, when 'n' hidden layers use an activation like the sigmoid function, 'n' small derivatives are multiplied. Thus, the gradient decreases exponentially as we propagate down to the initial layers.\nHow to fix it? 1. Use non-saturating activation function because of the nature of the sigmoid activation function, it starts saturating for larger inputs (negative or positive) came out to be a major reason behind the vanishing of gradients, thus making it non-recommendable to use in the hidden layers of the network.\nSo to tackle the issue regarding the saturation of activation functions like sigmoid and tanh, we must use some other non-saturating functions like ReLU and its alternatives.\n2. Proper weight initialization There are different ways to initialize weights, for example, Xavier / Glorot initialization, Kaiming initializer etc. Keras API has a default weight initializer for each type of layers. For example, see the available initializers for tf.keras in Keras doc.\nYou can get the weights of a layer like below:\n1# tf.Keras 2model.layers[1].get_weights() Using Xavier normal initializer with Keras:\n1initializer = tf.keras.initializers.GlorotNormal() 2layer = tf.keras.layers.Dense(3, kernel_initializer=initializer) This option does not guarantee that you will resolve these issues, but it makes your network more robust when combined with other methods. Residual networks.\n3. Residual networks If you are using Convolutional Neural Networks, for example, and you are suffering from vanishing / exploding gradients, it might make sense to move to a new architecture like ResNETs. Compared to other networks, these structures connect different layers, i.e., the so-called skip connections, acting as gradient highways, allowing the gradient to flow between the different layers unhindered.\n4. Batch normalization (BN) BN layers can also resolve the issue. As stated before, the problem arises when a large input space is mapped to a small one, causing the derivatives to disappear. Batch normalization reduces this problem by simply normalizing the input, so it doesn’t reach the outer edges of the sigmoid function. Example of using BN with TensorFlow\n1from keras.layers.normalization import BatchNormalization 2 3# instantiate model 4model = Sequential() 5 6# The general use case is to use BN between the linear and non-linear layers in your network, 7# because it normalizes the input to your activation function, 8# though, it has some considerable debate about whether BN should be applied before 9# non-linearity of current layer or works best after the activation function. 10 11model.add(Dense(64, input_dim=14, init=\u0026#39;uniform\u0026#39;)) # linear layer 12model.add(BatchNormalization()) # BN 13model.add(Activation(\u0026#39;tanh\u0026#39;)) # non-linear layer Batch normalization applies a transformation that maintains the mean output close to 0 and the output standard deviation close to 1.\nReference:\nhttps://datascience.stackexchange.com/a/72352/136830 https://towardsdatascience.com/the-vanishing-gradient-problem-69bf08b15484 https://keras.io/api/layers/initializers/#usage-of-initializers https://machinelearningmastery.com/how-to-fix-vanishing-gradients-using-the-rectified-linear-activation-function/ ","link":"https://blog.sksoumik.com/artificial-intelligence/vanishing-gradient_problem/","section":"artificial-intelligence","tags":["deep learning","machine learning","data science"],"title":"Vanishing Gradient Problem and How to Fix it"},{"body":"Combine two lists as a dictionary | dict(zip) Program\n1keys = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;] 2values = [1, 2, 3] 3dictionary = dict(zip(keys, values)) 4print(dictionary) Output\n1{\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2, \u0026#39;c\u0026#39;: 3} Program\n1keys = (\u0026#39;name\u0026#39;, \u0026#39;age\u0026#39;, \u0026#39;location\u0026#39;) 2values = (\u0026#39;Soumik\u0026#39;, 26, \u0026#39;Bangladesh\u0026#39;) 3 4new_dict = dict(zip(keys, values)) 5print(new_dict) Output\n1# output 2{\u0026#39;name\u0026#39; : \u0026#39;Soumik\u0026#39;, \u0026#39;age\u0026#39; : 26, \u0026#39;location\u0026#39; : \u0026#39;Bangladesh\u0026#39;} Create nested directory Program\n1from pathlib import Path 2 3Path(\u0026#34;father/child\u0026#34;).mkdir(parents=True, exist_ok=True) We can also the os module.\n1import os 2 3TARGET_DIR = \u0026#34;parent_dir/child_dir\u0026#34; 4if not os.path.exists(TARGET_DIR): 5 os.makedirs(TARGET_DIR) Slice Strings Program\n1# print the last character of the string 2text = \u0026#34;abcd\u0026#34; 3text[-1] Output\n1\u0026#39;d\u0026#39; Program\n1# print everything but the last character 2text = \u0026#34;abcd\u0026#34; 3text[:-1] # text[0: -1] Output\n1\u0026#39;abc\u0026#39; Reverse a string using recursion 1def reverse_text(text): 2 # base condition 3 if text == \u0026#34;\u0026#34;: 4 return text 5 else: 6 return text[-1] + reverse_text(text[0:-1]) append and extend in Python List Program\n1x = [1, 2, 3] 2x.append([4, 5]) 3print (x) Output\n1[1, 2, 3, [4, 5]] Program\n1x = [1, 2, 3] 2x.extend([4, 5]) 3print (x) Output\n1[1, 2, 3, 4, 5] Program\n1my_list = [\u0026#39;Python\u0026#39;, \u0026#39;Java\u0026#39;] 2my_list.append(\u0026#39;Dart\u0026#39;) 3print(my_list) Output\n1[\u0026#39;Python\u0026#39;, \u0026#39;Java\u0026#39;, \u0026#39;Dart\u0026#39;] Program\n1my_list = [\u0026#39;python\u0026#39;, \u0026#39;java\u0026#39;] 2another_list = [0, 1, 2, 3] 3my_list.extend(another_list) 4print(my_list) Output\n1[\u0026#39;python\u0026#39;, \u0026#39;java\u0026#39;, 0, 1, 2, 3] Flatten a 2D matrix to a 1D matrix 1matrix = [[1,5,9],[10,11,13],[12,13,15]] 2flat_matrix = sum(matrix, []) output\n1[1, 5, 9, 10, 11, 13, 12, 13, 15] Check any number exists or not in a list 1nums = [3, 4, 5, 6, 7, 8] 2print(4 in nums) output\n1True Switch case in Python Unlike every other programming language, Python does not have a switch or case statement.To get around this fact, we use dictionary mapping.\nProgram\n1def numbers_to_strings(argument): 2 # argument: key of a dictionary 3 4 switcher = { 5 0: \u0026#34;zero\u0026#34;, 6 1: \u0026#34;one\u0026#34;, 7 2: \u0026#34;two\u0026#34;, 8 } 9 return switcher.get(argument, \u0026#34;Data not available\u0026#34;) 10 11 12if __name__ == \u0026#34;__main__\u0026#34;: 13 14 result = numbers_to_strings(1) 15 print(result) Output\n1one Program\n1def numbers_to_strings(argument): 2 switcher = { 3 0: \u0026#34;zero\u0026#34;, 4 1: \u0026#34;one\u0026#34;, 5 2: \u0026#34;two\u0026#34;, 6 } 7 return switcher.get(argument, \u0026#34;Data not available\u0026#34;) 8 9 10if __name__ == \u0026#34;__main__\u0026#34;: 11 12 result = numbers_to_strings(4) 13 print(result) Output\n1Data not available Read and Write File Program\n1# read mode only, if the file does not exists, raises I/O error 2 3filename = open(\u0026#34;new_file.txt\u0026#34;, \u0026#34;a\u0026#34;) Reverse a list program\n1language = [\u0026#34;Python\u0026#34;, \u0026#34;Java\u0026#34;, \u0026#34;Dart\u0026#34;] 2language.reverse() 3print(language) Output\n1[\u0026#39;Dart\u0026#39;, \u0026#39;Java\u0026#39;, \u0026#39;Python\u0026#39;] Merge two lists Program\n1num1 = [4, 5, 6] 2num2 = [5, 6, 7] 3 4result = num1 + num2 5print(result) Output\n1[4, 5, 6, 5, 6, 7] Find the common items among multiple lists 2D lists:\n1edges = [ 2\t[1,2], 3\t[2,3], 4\t[4,2] 5] 6 7common_eleme = set.intersection(*map(set, edges)) 8 9for item in common_eleme: 10 print(item) # 2 1D lists\n1ar1 = [1, 5, 10, 20, 40, 80] 2ar2 = [6, 7, 20, 80, 100] 3ar3 = [3, 4, 15, 20, 30, 70, 80, 120] 4 5# find the common elements in the three arrays 6common_eleme = set.intersection(*map(set, [ar1, ar2, ar3])) 7 8for item in common_eleme: 9 print(item) # 80 20 Generators in Python The main advantage of generator over a list is that it takes much less memory. The syntax for generators and list comprehensions:\n1 L = [1, 2,3,4] 2\u0026gt;\u0026gt;\u0026gt; [x**x for x in L] 3[1, 4, 27, 256] 4\u0026gt;\u0026gt;\u0026gt; (x**x for x in L) 5\u0026lt;generator object \u0026lt;genexpr\u0026gt; at 0x7fa9fb5aac10\u0026gt; When to use what?\nYou should use a list if you want to use any of the list methods. For example, the following code won't work:\n1def gen(): 2 return (something for something in get_some_stuff()) 3 4print gen()[:2] # generators don\u0026#39;t support indexing or slicing 5print [5,6] + gen() # generators can\u0026#39;t be added to lists Basically, use a generator expression if all you're doing is iterating once.\nIf you want to store and use the generated results, then you're probably better off with a list comprehension.\nUsing generators inside functions Program\n1x = sum(i for i in range(10)) 2print(x) Output\n145 Transpose a matrix Program\n1x = [[31, 17], [40, 51], [13, 12]] 2print(list(zip(*x))) Output\n1[(31, 40, 13), (17, 51, 12)] Find the common prefix for a list of strings Program\n1import os 2 3common = os.path.commonprefix([\u0026#34;flower\u0026#34;, \u0026#34;flow\u0026#34;, \u0026#34;flight\u0026#34;]) 4print(common) Output\n1fl Using map in Python Program\n1def fn_square(number): 2 return number ** 2 3 4 5if __name__ == \u0026#34;__main__\u0026#34;: 6 lst = [1, 2, 3, 4] 7 # map(function, a iterable) 8 square = map(fn_square, lst) 9 result = list(square) 10 print(result) Output\n1[1, 4, 9, 16] Using Lamda Program\n1 2iterable = [1, 2, 3, 4] 3 4square = map(lambda x: x ** 2, iterable) 5result = list(square) 6print(result) Output\n1[1, 4, 9, 16] Multiple list Program\n1num1 = [4, 5, 6] 2num2 = [5, 6, 7] 3 4summation = map(sum, zip(num1, num2)) 5print(list(summation)) Output\n1[9, 11, 13] Add as many lists you want Program\n1def sum_lists(*args): 2 return list(map(sum, zip(*args))) 3 4 5a = [1, 2, 3] 6b = [1, 2, 3] 7c = [2, 3, 4] 8 9result = sum_lists(a, b, c) 10print(result) Output\n1[4, 7, 10] kwargs in Python Program\n1def information(**data): 2 for key, value in data.items(): 3 print(f\u0026#34;{key}: {value}\u0026#34;) 4 5 print() 6 7 8if __name__ == \u0026#34;__main__\u0026#34;: 9 information(Firstname=\u0026#34;Sadman\u0026#34;, Lastname=\u0026#34;Soumik\u0026#34;, Age=26, Phone=1234567890) 10 information( 11 Firstname=\u0026#34;John\u0026#34;, 12 Lastname=\u0026#34;Wood\u0026#34;, 13 Email=\u0026#34;johnwood@nomail.com\u0026#34;, 14 Country=\u0026#34;Wakanda\u0026#34;, 15 Age=25, 16 Phone=9876543210, 17 ) Output\n1Firstname: Sadman 2Lastname: Soumik 3Age: 26 4Phone: 1234567890 5 6Firstname: John 7Lastname: Wood 8Email: johnwood@nomail.com 9Country: Wakanda 10Age: 25 11Phone: 9876543210 Check the memory usage Program\n1import sys 2 3a, b, c, d = \u0026#34;abcde\u0026#34;, \u0026#34;xy\u0026#34;, 2, 15.06 4print(sys.getsizeof(a)) 5print(sys.getsizeof(b)) 6print(sys.getsizeof(c)) 7print(sys.getsizeof(d)) Check if a file exists Program\n1import os.path 2 3if os.path.isfile(filepath): 4 print(\u0026#34;File exists\u0026#34;) Merge two dictionaries Program\n1x = {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2} 2y = {\u0026#39;b\u0026#39;: 3, \u0026#39;c\u0026#39;: 4} 3z = {**x, **y} 4print(z) Output\n1{\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 3, \u0026#39;c\u0026#39;: 4} Make a flat list list out of lists of lists Program\n1import itertools 2 3list_2d = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] 4merged = list(itertools.chain(*list_2d)) 5print(merged) Output\n1[1, 2, 3, 4, 5, 6, 7, 8, 9] Program\n1# using list comprehension 2 3list_2d = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] 4flat_list = [item for sublist in list_2d for item in sublist] 5print(flat_list) Output\n1[1, 2, 3, 4, 5, 6, 7, 8, 9] Produce reversed list Program\n1for i in range(10, -1, -1): 2 print(i, end=\u0026#34; \u0026#34;) Output\n110 9 8 7 6 5 4 3 2 1 0 Slicing in array Program\n1a[-1] # last item in the array 2a[-2:] # last two items in the array 3a[:-2] # everything except the last two items 4 5a[::-1] # all items in the array, reversed Find the index of an item in an array using Python Program\n1list = [\u0026#34;Tensorflow\u0026#34;, \u0026#34;PyTorch\u0026#34;, \u0026#34;Caffe\u0026#34;] 2idx_pytorch = list.index(\u0026#34;PyTorch\u0026#34;) 3print(idx_pytorch) Output\n11 minimum len/number in a list 1a = [1, 5, 6, 2, 3, 4] 2print(min(a)) # 1 3 4b = [\u0026#34;flower\u0026#34;, \u0026#34;flow\u0026#34;, \u0026#34;flight\u0026#34;] 5smallest_str = min(b, key=len) 6print(smallest_str) # flow Iterating over dictionaries using 'for' loops Program\n1d = {\u0026#34;x\u0026#34;: 1, \u0026#34;y\u0026#34;: 2, \u0026#34;z\u0026#34;: 3} 2 3for key, value in d.items(): 4 print(key, value) Output\n1x 1 2y 2 3z 3 Sort a dictionary by key in ascending order in Python Program\n1d = {1: \u0026#34;a\u0026#34;, 3: \u0026#34;d\u0026#34;, 4: \u0026#34;c\u0026#34;, 2: \u0026#34;b\u0026#34;, 0: \u0026#34;e\u0026#34;} 2 3sorted_dict = sorted(d.items(), key=lambda x: x[0]) 4print(dict(sorted_dict)) Output\n1{0: \u0026#39;e\u0026#39;, 1: \u0026#39;a\u0026#39;, 2: \u0026#39;b\u0026#39;, 3: \u0026#39;d\u0026#39;, 4: \u0026#39;c\u0026#39;} Sort a dictionary by key in descending order in Python Program\n1sorted_dict = sorted(d.items(), key=lambda x: x[0], reverse=True) 2print(dict(sorted_dict)) Output\n1{4: \u0026#39;c\u0026#39;, 3: \u0026#39;d\u0026#39;, 2: \u0026#39;b\u0026#39;, 1: \u0026#39;a\u0026#39;, 0: \u0026#39;e\u0026#39;} Sort a diction\nSort a dictionary by value in ascending order in Python Program\n1sorted_dict = sorted(d.items(), key=lambda x: x[1]) 2print(dict(sorted_dict)) Output\n1{1: \u0026#39;a\u0026#39;, 2: \u0026#39;b\u0026#39;, 4: \u0026#39;c\u0026#39;, 3: \u0026#39;d\u0026#39;, 0: \u0026#39;e\u0026#39;} # sorted by value Rename all files of a folder in Python 1import os 2 3os.getcwd() 4src_path = \u0026#34;./source_folder/\u0026#34; 5destination_path = \u0026#34;./destination_folder/\u0026#34; 6 7for i, filename in enumerate(os.listdir(src_path)): 8 os.rename(src_path + filename, destination_path + str(i) + \u0026#34;.jpg\u0026#34;) Count distinct elements in a list Program\n1from collections import Counter 2 3words = [\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;a\u0026#34;] 4 5print(dict(Counter(words))) 6# {\u0026#39;a\u0026#39;: 3, \u0026#39;b\u0026#39;: 2, \u0026#39;c\u0026#39;: 1} 7print(list(Counter(words).keys())) 8# [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;] 9print(list(Counter(words).values())) 10# [3, 2, 1] Most frequent element in a list Program\n1from collections import Counter 2 3 4def most_frequent(lst): 5 data = Counter(lst) 6 return data.most_common(1) # returns most frequent 1 element 7 8 9list = [2, 1, 2, 2, 1, 3, 3, 3, 2] 10print(most_frequent(list)) Output\n1[(2, 4)] # means, 2 is the most frequent element which appears 4 times. Program\n1from collections import Counter 2 3 4def most_frequent(lst): 5 data = Counter(lst) 6 return data.most_common(2) # returns most frequent 2 elements 7 8 9list = [2, 1, 2, 2, 1, 3, 3, 3, 2] 10print(most_frequent(list)) Output\n1[(2, 4), (3, 3)] # 2 -\u0026gt; 4 times; 3 -\u0026gt; 3 times Program\n1from collections import Counter 2 3 4def most_frequent(lst): 5 data = Counter(lst) 6 return data.most_common(1)[0][0] # [0][0] is the first element of a matrix 7 8list = [2, 1, 2, 2, 1, 3, 3, 3, 2] 9print(most_frequent(list)) Output\n12 Find the duplicate elements in a list Program\n1from collections import Counter 2 3 4def find_duplicate(values): 5 duplicates = Counter(values) - Counter(set(values)) 6 return list(duplicates.keys()) 7 8 9if __name__ == \u0026#34;__main__\u0026#34;: 10 values = [1, 2, 3, 3, 3, 4, 5, 6, 6, 7] 11 print(find_duplicate(values)) Output\n1[3, 6] range(9, -1, -1)Collections Module Create a class using namedtuple Program\n1from collections import namedtuple 2 3# create an Employee class 4Employee = namedtuple(\u0026#34;Employee\u0026#34;, [\u0026#34;name\u0026#34;, \u0026#34;position\u0026#34;, \u0026#34;level\u0026#34;]) 5print(Employee) # \u0026lt;class \u0026#39;__main__.Employee\u0026#39;\u0026gt; 6 7# assign names in the Employee class 8employee_1 = Employee(\u0026#34;Mr. Smith\u0026#34;, \u0026#34;Software Engineer\u0026#34;, \u0026#34;junior\u0026#34;) 9print(employee_1) 10# Employee(name=\u0026#39;Mr. Smith\u0026#39;, position=\u0026#39;Software Engineer\u0026#39;, level=\u0026#39;junior\u0026#39;) 11 12print(employee_1.position) # Software Engineer 13print(dict(employee_1._asdict())) 14# {\u0026#39;name\u0026#39;: \u0026#39;Mr. Smith\u0026#39;, \u0026#39;position\u0026#39;: \u0026#39;Software Engineer\u0026#39;, \u0026#39;level\u0026#39;: \u0026#39;junior\u0026#39;} Create dictionaries using defaultdict 1from collections import defaultdict 2 3employee_record = [ 4 (\u0026#34;Kabir\u0026#34;, \u0026#34;ML\u0026#34;, \u0026#34;level-b\u0026#34;), 5 (\u0026#34;Sunehra\u0026#34;, \u0026#34;SDE\u0026#34;, \u0026#34;level-b\u0026#34;), 6 (\u0026#34;Smith\u0026#34;, \u0026#34;ML\u0026#34;, \u0026#34;level-c\u0026#34;), 7 (\u0026#34;William\u0026#34;, \u0026#34;HR\u0026#34;, \u0026#34;level-c\u0026#34;), 8] 9 10employee_name_by_dept = defaultdict(list) 11print(employee_name_by_dept) 12# defaultdict(\u0026lt;class \u0026#39;list\u0026#39;\u0026gt;, {}) 13 14for name, dept, level in employee_record: 15 employee_name_by_dept[dept].append(name) # dept as key, name as values 16 17print(dict(employee_name_by_dept)) 18# {\u0026#39;ML\u0026#39;: [\u0026#39;Kabir\u0026#39;, \u0026#39;Smith\u0026#39;], \u0026#39;SDE\u0026#39;: [\u0026#39;Sunehra\u0026#39;], \u0026#39;HR\u0026#39;: [\u0026#39;William\u0026#39;]} Inserting elements in a list Program\n1employee_list = [\u0026#34;Soumik\u0026#34;, \u0026#34;Jamie\u0026#34;, \u0026#34;Smith\u0026#34;] 2 3# O(n) performance 4employee_list.insert(0, \u0026#34;Sunehra\u0026#34;) 5print(employee_list) Output\n1[\u0026#39;Sunehra\u0026#39;, \u0026#39;Soumik\u0026#39;, \u0026#39;Jamie\u0026#39;, \u0026#39;Smith\u0026#39;] Program\n1from collections import deque 2 3 4employee_list = [\u0026#34;Soumik\u0026#34;, \u0026#34;Jamie\u0026#34;, \u0026#34;Smith\u0026#34;] 5employee_list_deque = deque(employee_list) 6 7# O(1) time performance 8employee_list_deque.appendleft(\u0026#34;Sunehra\u0026#34;) 9print(list(employee_list_deque)) Output\n1[\u0026#39;Sunehra\u0026#39;, \u0026#39;Soumik\u0026#39;, \u0026#39;Jamie\u0026#39;, \u0026#39;Smith\u0026#39;] Note\nAlthough deque adds entries to the beginning of a sequence more efficiently than a list, deque does not perform all of its operations more efficiently than a list. For example, accessing a random item in a deque has O(n) performance, but accessing a random item in a list has O(1) performance.\nUse deque when it is important to insert or remove elements from either side of your collection quickly.\nMap multiple dictionary Program\n1from collections import ChainMap 2 3salary = {\u0026#34;SDE\u0026#34;: 100000, \u0026#34;HR\u0026#34;: 80000, \u0026#34;MTO\u0026#34;: 60000} 4office_hq = {\u0026#34;Asia\u0026#34;: \u0026#34;Singapore\u0026#34;, \u0026#34;Europe\u0026#34;: \u0026#34;Dublin\u0026#34;, \u0026#34;North America\u0026#34;: \u0026#34;USA\u0026#34;} 5age_limit = {\u0026#34;SDE\u0026#34;: 40, \u0026#34;HR\u0026#34;: 50} 6 7employee_info = ChainMap(salary, office_hq, age_limit) 8print(employee_info.maps) Output\n1[ 2 {\u0026#39;SDE\u0026#39;: 100000, \u0026#39;HR\u0026#39;: 80000, \u0026#39;MTO\u0026#39;: 60000}, 3 {\u0026#39;Asia\u0026#39;: \u0026#39;Singapore\u0026#39;, \u0026#39;Europe\u0026#39;: \u0026#39;Dublin\u0026#39;, \u0026#39;North America\u0026#39;: \u0026#39;USA\u0026#39;}, 4 {\u0026#39;SDE\u0026#39;: 40, \u0026#39;HR\u0026#39;: 50} 5] Ordered dictionary 1import collections 2 3# remembers the order 4d = collections.OrderedDict() 5d[\u0026#34;A\u0026#34;] = 65 6d[\u0026#34;C\u0026#34;] = 67 7d[\u0026#34;B\u0026#34;] = 66 8d[\u0026#34;D\u0026#34;] = 68 9 10for key, value in d.items(): 11 print(key, value) Output\n1A 65 2C 67 3B 66 4D 68 Remove space and newlines from strings Program\n1s = \u0026#34; \\n\\r\\n \\n abc def \\n\\r\\n \\n \u0026#34; 2remove_all = s.strip() 3remove_left = s.lstrip() 4remove_right = s.rstrip() 5 6print(remove_all) # \u0026#39;abc def\u0026#39; 7print(remove_left) # \u0026#39;abc def \\n\\r\\n \\n \u0026#39; 8print(remove_right) # \u0026#39; \\n\\r\\n \\n abc def\u0026#39; Limit floats to two decimal places Program\n1a = 13.946 2print(\u0026#34;%.2f\u0026#34; % a) Output\n113.95 Program\n1x = 13.946 2print(round(x, 2)) Output\n113.95 Randomly select an item from an list. 1import random 2 3foo = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;] 4print(random.choice(foo)) Create a single string from all the elements in a list Program\n1a = [\u0026#34;Data\u0026#34;, \u0026#34;Science\u0026#34;, \u0026#34;Expert\u0026#34;] 2full_str = \u0026#34; \u0026#34;.join(a) 3print(full_str) Output\n1Data Science Expert Program\n1a = [\u0026#34;Data\u0026#34;, \u0026#34;Science\u0026#34;, \u0026#34;Expert\u0026#34;] 2full_str = \u0026#34;, \u0026#34;.join(a) 3print(full_str) Output\n1Data, Science, Expert List vs Tuple List tuple A strong culture among python communities is to store homogeneous data ins list Strong culture in python communities Used to store heterogeneous data in tuples. example: l = [1, 2, 3, 4, 5] example: t = (1, a, 3, d, X) Mutable: You can always change a list after data assignment. Immutable: You can't change it after assignment. Common operations: append, extend, insert, remove, pop, reverse, count, copy, clear Methods that add items or remove items are not available with tuple. [count and index] Set: Set is unordered and contains no duplicates, which makes it very useful for math operations like unions and intersections. Whereas, List and Tuples are Ordered, and contains duplicate elements.\nyield keyword yield is a keyword that is used like return, except the function will return a generator.\nGenerators do not store all the values in memory, they generate the values on the fly.\nInheritance in Python Program\n1# define the base class 2 3class Person: 4 def __init__(self, first_name, last_name): 5 self.first_name = first_name 6 self.last_name = last_name 7 8 def print_name(self): 9 print(self.first_name, self.last_name) 10 11 12x = Person(\u0026#34;Elon\u0026#34;, \u0026#34;Musk\u0026#34;) 13x.print_name() Output\n1Elon Musk Program\n1# create a subclass (Entrepreneur) that extends base class(Person) 2 3class Entrepreneur(Person): 4 pass Program\n1# Use the Entrepreneur class to create an object, 2# and then execute the print_name method 3 4 5sub_class_var = Entrepreneur(\u0026#34;Elon\u0026#34;, \u0026#34;Musk\u0026#34;) 6sub_class_var.print_name() Output\n1Elon Musk Program\n1# When we add the __init__() function, the subclass will 2# no longer inherit the parent\u0026#39;s/base\u0026#39;s __init__() function 3 4 5class Entrepreneur(Person): 6 def __init__(self, first_name, last_name): 7 # add properties 1# we can add super() function that will make the child class 2# inherit all the methods and properties from its parent + 3# we can add it\u0026#39;s own properties. 4 5 6class Entrepreneur(Person): 7 def __init__(self, first_name, last_name): 8 super().__init__(first_name, last_name) Program\n1class Entrepreneur(Person): 2 def __init__(self, first_name, last_name): 3 super().__init__(first_name, last_name) 4 5 # adding new properties in the subclass 6 self.company_name = \u0026#34;SpaceX\u0026#34; Polymorphism in Python Program\n1class Vehicle: 2 # Constructor of the class 3 def __init__(self, name): 4 self.name = name 5 6 # Abstract method, defined by convention only 7 def brand(self): 8 raise NotImplementedError(\u0026#34;Subclass must implement abstract method\u0026#34;) 9 10 11class Car(Vehicle): 12 def brand(self): 13 return f\u0026#34;Car name: {self.name}\u0026#34; 14 15 16class Bike(Vehicle): 17 def brand(self): 18 return f\u0026#34;Bike name: {self.name}\u0026#34; 19 20 21if __name__ == \u0026#34;__main__\u0026#34;: 22 vehicles = [Car(\u0026#34;BMW\u0026#34;), Car(\u0026#34;Audi\u0026#34;), Bike(\u0026#34;Bajaj\u0026#34;)] 23 24 for vehicle in vehicles: 25 print(vehicle.brand()) Output\n1Car name: BMW 2Car name: Audi 3Bike name: Bajaj Static methods in python Program\n1class MyClass: 2 @staticmethod 3 def the_static_method(x): 4 print(x) 5 6 7MyClass.the_static_method(2) # outputs 2 Description\nWe can have static method in Python using @staticmethod decorator. Like other static methods in other languages, we don't need to create class instance to call the static method. We can directly call the static method using the Class name. Static methods are usually used to create utility functions.\nDunder methods/Magic Functions Program\n1class PrintString: 2 def __init__(self, str): 3 self.str = str 4 5 6if __name__ == \u0026#34;__main__\u0026#34;: 7 string_1 = PrintString(\u0026#34;Dunder Methods\u0026#34;) 8 print(string_1) Output\n1\u0026lt;__main__.PrintString object at 0x7fa4c1709190\u0026gt; 2# prints only the memory address of the string object Program\n1class PrintString: 2 def __init__(self, str): 3 self.str = str 4 5 def __repr__(self): 6 return f\u0026#34;String: {self.str}\u0026#34; 7 8 9if __name__ == \u0026#34;__main__\u0026#34;: 10 string_1 = PrintString(\u0026#34;Dunder Methods\u0026#34;) 11 print(string_1) Output\n1String: Dunder Methods Program\n1class PrintString: 2 def __init__(self, str): 3 self.str = str 4 5 def __repr__(self): 6 return f\u0026#34;String: {self.str}\u0026#34; 7 8 9if __name__ == \u0026#34;__main__\u0026#34;: 10 string_1 = PrintString(\u0026#34;Dunder Methods\u0026#34;) 11 12 # try to add another string with it 13 string_2 = string_1 + \u0026#34;Magic Methods\u0026#34; 14 print(string_2) Output\n1Traceback (most recent call last): 2 File \u0026#34;test_code.py\u0026#34;, line 12, in \u0026lt;module\u0026gt; 3 string_2 = string_1 + \u0026#34;Magic Methods\u0026#34; 4TypeError: unsupported operand type(s) for +: \u0026#39;PrintString\u0026#39; and \u0026#39;str\u0026#39; Program\n1class PrintString: 2 def __init__(self, str): 3 self.str = str 4 5 def __repr__(self): 6 return f\u0026#34;String: {self.str}\u0026#34; 7 8 def __add__(self, other): 9 return self.str + \u0026#34; \u0026#34; + other 10 11 12if __name__ == \u0026#34;__main__\u0026#34;: 13 string_1 = PrintString(\u0026#34;Dunder Methods\u0026#34;) 14 string_2 = string_1 + \u0026#34;Magic Methods\u0026#34; 15 print(string_2) Output\n1Dunder Methods Magic Methods str 1class Employee: 2 def __init__(self, name, designation): 3 self.name = name 4 self.designation = designation 5 6 def get_name(self): 7 return self.name 8 9 def get_designation(self): 10 return self.designation 11 12 def print_info(self): 13 return f\u0026#34;Name: {self.name}, Position: {self.position}\u0026#34; 14 15 16if __name__ == \u0026#34;__main__\u0026#34;: 17 emp_1 = Employee(\u0026#34;Jeff Bezos\u0026#34;, \u0026#34;CEO\u0026#34;) 18 print(emp_1) Output\n1\u0026lt;__main__.Employee object at 0x7f82303b5390\u0026gt; 2# prints memory address Program\n1class Employee: 2 def __init__(self, name, designation): 3 self.name = name 4 self.designation = designation 5 6 def get_name(self): 7 return self.name 8 9 def get_designation(self): 10 return self.designation 11 12 # use only __str__ instead of print_info 13 def __str__(self): 14 return f\u0026#34;Name: {self.name}, Position: {self.designation}\u0026#34; 15 16 17if __name__ == \u0026#34;__main__\u0026#34;: 18 emp_1 = Employee(\u0026#34;Jeff Bezos\u0026#34;, \u0026#34;CEO\u0026#34;) 19 print(emp_1) 1Name: Jeff Bezos, Position: CEO # prints the string Note:\nPython has two different ways to convert an object to a string: str() and repr()\nDefine __repr__ for objects you write so you and other developers have a reproducible example when using it as you develop. Define __str__ when you need a human readable string representation of it.\nRead JSON file 1import json 2 3def load_data(file): 4 intents = json.loads(open(file).read()) 5 return intents 6 7json_file = load_data(\u0026#39;filename.json\u0026#39;) Common List Operations in Python append | extend\n1x = [1, 2, 3, 4] 2x.append(5) 3print(x) # [1, 2, 3, 4, 5] 4 5y = [6, 7, 8] 6x.extend(y) # y should be iterable, not int/str 7print(x) # [1, 2, 3, 4, 5, 6, 7, 8] 8 9 10x.insert(0, 10) # insert 10, at position 0 11print(x) # [10, 1, 2, 3, 4, 5, 6, 7, 8] 12 13x.insert(len(x), 20) # insert 20 at the end of the list 14print(x) # [10, 1, 2, 3, 4, 5, 6, 7, 8, 20] reverse\n1 2x = [1, 2, 3, 4] 3 4print(x[::-1]) # [4, 3, 2, 1] ; doesn\u0026#39;t chnage the original list 5print(x) # [1, 2, 3, 4] 6 7x.reverse() 8print(x) # [4, 3, 2, 1] ; change the original list inplace count\n1x = [1, 2, 3, 4, 1, 1] 2 3print(x.count(1)) # 3 clear\n1x = [1, 2, 3, 4, 1, 1] 2x.clear() 3print(x) # [] index\n1x = [\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;d\u0026#34;, \u0026#34;e\u0026#34;, \u0026#34;f\u0026#34;] 2print(x.index(\u0026#34;d\u0026#34;)) # 3 Split a list into x amounts 1x = [1,2,3,4,5,6,7,1,2,3,3,3,3,3,3,3,3,3] 2# split the above list into 8 parts 3split_x = [x[i::8] for i in range(8)] 4print(split_x) output:\n1[[1, 2, 3], [2, 3, 3], [3, 3], [4, 3], [5, 3], [6, 3], [7, 3], [1, 3]] flatten the split_x:\n1flat_x = [item for sublist in split_x for item in sublist] 2print(flat_x) output:\n1[1, 2, 3, 2, 3, 3, 3, 3, 4, 3, 5, 3, 6, 3, 7, 3, 1, 3] Save all items of a list in a line separated text file 1lst = [\u0026#39;Sample text 1\u0026#39;, \u0026#39;sample text 2\u0026#39;, \u0026#39;sample text 3\u0026#39;, \u0026#39;sample text 4\u0026#39;] 2 3SAVE_PATH = \u0026#39;./my_list.txt\u0026#39; 4 5with open(SAVE_PATH, mode=\u0026#39;wt\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as myfile: 6 myfile.write(\u0026#39;\\n\u0026#39;.join(lst)) Take multiple user inputs Take two int inputs 1a, b = map(int, input().split()) 2print(f\u0026#34;a = {a}; b = {b}\u0026#34;) output:\n110 20 # user input 2a = 10; b = 20 Input a list of integers 1l = list(map(int, input().split())) 2print(l) output:\n110 20 30 40 # user input 2[10, 20, 30, 40] Input a list of strings 1l = list(map(str, input().split())) 2print(l) output:\n1apple google facebook # user input 2[\u0026#39;apple\u0026#39;, \u0026#39;google\u0026#39;, \u0026#39;facebook\u0026#39;] Create some random numbers 1import random 2 3sample_list = [] 4 5for _ in range(100): 6\t# create 100 integers in the range of [10, 1000] 7 sample_list.append(random.randint(10, 1000)) Randomly select 10 items from the sample_list 1selected_sample = random.sample(sample_list, 10) 2print(selected_sample) Sort a dictionary by its value in ascending order 1def sort_dict_by_value(d): 2 return sorted(d.items(), key=lambda x: x[1], reverse=False) 3 4 5if __name__ == \u0026#34;__main__\u0026#34;: 6 d = {\u0026#34;a\u0026#34;: 1, \u0026#34;b\u0026#34;: 2, \u0026#34;c\u0026#34;: 3} 7 print(sort_dict_by_value(d)) output\n1[(\u0026#39;a\u0026#39;, 1), (\u0026#39;b\u0026#39;, 2), (\u0026#39;c\u0026#39;, 3)] Print colored text in the terminal 1# pip install termcolor 2 3from termcolor import colored, cprint 4 5text = colored(\u0026#39;Hello, World!\u0026#39;, \u0026#39;red\u0026#39;) 6print(text) 7cprint(\u0026#39;Hello, World!\u0026#39;, \u0026#39;red\u0026#39;, \u0026#39;on_yellow\u0026#39;) output\nwhy lists have .append and sets have .add .append means to add to the end, which is accurate and makes sense for lists, but sets have no notion of ordering and hence no beginning or end, so .add makes more sense for them.\nremove duplicates from a list preserving original order? 1\u0026gt;\u0026gt;\u0026gt; items = [1, 2, 0, 1, 3, 2] 2\u0026gt;\u0026gt;\u0026gt; list(dict.fromkeys(items)) 3[1, 2, 0, 3] Disable all warnings 1!pip install shutup 2 3import shutup 4shutup.please() Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/python-code-snippets-tricks-tips/","section":"software-engineering","tags":["algorithms","leetcode","problem solving"],"title":"Be a Master at Python Through Some Cool Code Snippets"},{"body":"There are several types of ensemble techniques in machine learning, including: Bagging, Boosting, Stacking, Blending, Bootstrapped ensembles, Bayesian model averaging.\nBagging Bagging (short for bootstrapped aggregating) is an ensemble technique that involves training multiple models on different subsets of the training data, and then averaging the predictions of the individual models to make the final prediction. This can be done with decision trees, neural networks, or any other type of model. Classic example of bagging is Random Forest.\nThis is like having a group of people each give their own opinion, and then taking an average of all their answers. In machine learning, this is done by training multiple models on different subsets of the training data and then combining their predictions.\nOne of the main advantages of bagging is that it can reduce overfitting, which is when a model performs well on the training data but poorly on new, unseen data. By training multiple models on different subsets of the data, bagging can help to reduce the variance of the model, which can lead to improved generalization and better performance on new data.\nTo understand how bagging works, let's consider a simple example using decision trees. Suppose we have a dataset with 100 samples and 10 features. We can create 10 different subsets of the data by sampling with replacement from the original dataset. This means that some samples may appear in multiple subsets, while others may not appear at all. We can then train a decision tree model on each of the 10 subsets, resulting in 10 different models.\nTo make a prediction for a new sample, we can pass it through each of the 10 models and average the predictions. For example, if four of the models predict that the sample is positive, and six predict that it is negative, the final prediction would be negative.\nBagging is often used in conjunction with decision trees because decision trees are prone to overfitting. By training multiple decision trees on different subsets of the data, bagging can help to reduce the variance of the model and improve its generalization performance.\nHere's a Python code that demonstrates how to implement bagging using the scikit-learn library:\n1from sklearn.ensemble import BaggingClassifier 2from sklearn.tree import DecisionTreeClassifier 3 4# Create the base classifier 5base_classifier = DecisionTreeClassifier(max_depth=4) 6 7# Create the bagging classifier 8bagging_classifier = BaggingClassifier(base_classifier, n_estimators=10, max_samples=0.8, max_features=0.8) 9 10# Train the classifier on the training data 11bagging_classifier.fit(X_train, y_train) 12 13# Make predictions on the test data 14predictions = bagging_classifier.predict(X_test) In this example, we've created a base classifier using a decision tree with a maximum depth of 4. We've then created a bagging classifier that trains 10 decision trees on different subsets of the data (80% of the samples and 80% of the features). Finally, we've trained the classifier on the training data and made predictions on the test data.\nBagging is a simple and effective way to improve the performance of machine learning models by reducing overfitting and increasing the robustness of the model. It can be applied to a wide range of models, including decision trees, neural networks, and other types of models.\nBoosting Boosting is an ensemble technique that involves training a sequence of models, where each model tries to correct the mistakes of the previous model. The most common example of boosting is gradient boosting, which is often used for decision trees.\nThis is like having a group of people where each person gives their opinion based on what the previous person said. In machine learning, this is done by training multiple models sequentially, where each model tries to correct the mistakes of the previous model.\nOne of the main advantages of boosting is that it can improve the performance of weak models by combining their predictions in a way that reduces bias and variance. Boosting algorithms are based on the idea that it is possible to train a series of simple models that can be combined to form a more powerful model.\nTo understand how boosting works, let's consider a simple example using decision trees. Suppose we have a dataset with 100 samples and 10 features. We can train a decision tree model on the data, and then calculate the error of the model (the difference between the predicted output and the true output).\nNext, we can train a second decision tree model on the data, but this time, we weight the samples differently. For example, we might give higher weights to samples that were misclassified by the first model, and lower weights to samples that were correctly classified. By doing this, we are trying to correct the mistakes of the first model by training a new model that focuses more on the difficult samples.\nWe can repeat this process multiple times, training additional decision tree models on the data and weighting the samples differently at each iteration. The final prediction is made by combining the predictions of the individual models, typically by taking a weighted average.\nClassic example of boosting is Gradient Boosting Algorithms. XGBoost (XGBoost stands for eXtreme Gradient Boosting) is one of the most popular boosting algorithms.\nHere's a Python code that demonstrates how to implement gradient boosting using the scikit-learn library:\n1from sklearn.ensemble import GradientBoostingClassifier 2 3# Create the gradient boosting classifier 4gb = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, max_depth=3) 5 6# Train the classifier on the training data 7gb.fit(X_train, y_train) 8 9# Make predictions on the test data 10predictions = gb.predict(X_test) In this example, we've created a gradient boosting classifier that trains 100 decision trees with a learning rate of 0.1 and a maximum depth of 3. We've then trained the classifier on the training data and made predictions on the test data.\nStacking Stacking is an ensemble technique that involves training a sequence of models, where the output of each model is used as input to the next model in the sequence. The final prediction is made by a \u0026quot;meta-model\u0026quot; that takes the output of the other models as input.\nOne of the main advantages of stacking is that it can improve the performance of machine learning models by combining the predictions of multiple models in a way that reduces bias and variance. Stacking algorithms are based on the idea that it is possible to train a series of simple models, and then use a more powerful model to combine their predictions in a way that is more accurate than any individual model.\nTo understand how stacking works, let's consider a simple example using decision trees and logistic regression. Suppose we have a dataset with 100 samples and 10 features. We can train a decision tree model and a logistic regression model on the data, and then use the predictions of these models as input to a meta-model (in this case, another logistic regression model).\nTo train the meta-model, we create a new dataset that consists of the predictions of the decision tree and logistic regression models as features, and the true output as the target variable. We can then train the meta-model on this new dataset, and use it to make predictions on new samples.\nHere's a Python code that demonstrates how to implement stacking using the scikit-learn library:\n1from sklearn.ensemble import StackingClassifier 2from sklearn.tree import DecisionTreeClassifier 3from sklearn.linear_model import LogisticRegression 4 5# Create the base classifiers 6decision_tree = DecisionTreeClassifier(max_depth=4) 7logistic_regression = LogisticRegression() 8 9# Create the meta-model 10meta_model = LogisticRegression() 11 12# Create the stacking classifier 13stacking_classifier = StackingClassifier(estimators=[(\u0026#39;dt\u0026#39;, decision_tree), (\u0026#39;lr\u0026#39;, logistic_regression)], final_estimator=meta_model) 14 15# Train the classifier on the training data 16stacking_classifier.fit(X_train, y_train) 17 18# Make predictions on the test data 19predictions = stacking_classifier.predict(X_test) In this example, we've created a decision tree and a logistic regression model as the base classifiers, and another logistic regression model as the meta-model. We've then created a stacking classifier that combines the predictions of the base classifiers and uses the meta-model to make the final prediction. Finally, we've trained the classifier on the training data and made predictions on the test data.\nBlending Blending is an ensemble technique that involves training multiple models on the entire training set, and then averaging their predictions on the test set.\nTo understand how blending works, let's consider a simple example using decision trees and logistic regression. Suppose we have a dataset with 100 samples and 10 features. We can train a decision tree model and a logistic regression model on the entire training set, and then make predictions on the test set using each model.\nTo blend the predictions of the two models, we can simply take the average of their predictions. For example, if the decision tree model predicts that a sample is positive with probability 0.6, and the logistic regression model predicts that it is positive with probability 0.7, the blended prediction would be 0.65 (0.6 + 0.7)/2.\nHere's a Python code that demonstrates how to implement blending using the scikit-learn library:\n1from sklearn.tree import DecisionTreeClassifier 2from sklearn.linear_model import LogisticRegression 3 4# Create the decision tree and logistic regression models 5decision_tree = DecisionTreeClassifier(max_depth=4) 6logistic_regression = LogisticRegression() 7 8# Train the models on the training data 9decision_tree.fit(X_train, y_train) 10logistic_regression.fit(X_train, y_train) 11 12# Make predictions on the test data 13predictions_dt = decision_tree.predict_proba(X_test)[:,1] 14predictions_lr = logistic_regression.predict_proba(X_test)[:,1] 15 16# Blend the predictions 17predictions = (predictions_dt + predictions_lr)/2 In this example, we've created a decision tree and a logistic regression model, and trained them on the training data. We've then made predictions on the test data using each model, and blended the predictions by taking the average of the two.\nBootstrapped ensembles Bootstrapped ensembles is an ensemble technique that involves creating multiple versions of the training set by sampling with replacement, and then training a separate model on each version. The final prediction is made by averaging the predictions of the individual models.\nBootstrapped ensembles are based on the idea that it is possible to create multiple versions of the training set by sampling with replacement, and then train a separate model on each version. This can help to reduce the variance of the model, which can lead to improved generalization and better performance on new data.\nTo understand how bootstrapped ensembles work, let's consider a simple example using decision trees. Suppose we have a dataset with 100 samples and 10 features. We can create 10 different versions of the training set by sampling with replacement from the original dataset. This means that some samples may appear in multiple versions, while others may not appear at all. We can then train a decision tree model on each of the 10 versions, resulting in 10 different models.\nTo make a prediction for a new sample, we can pass it through each of the 10 models and average the predictions. For example, if four of the models predict that the sample is positive, and six predict that it is negative, the final prediction would be negative.\nHere's some example Python code that demonstrates how to implement bootstrapped ensembles using the scikit-learn library:\n1from sklearn.ensemble import BaggingClassifier 2from sklearn.tree import DecisionTreeClassifier 3 4# Create the base classifier 5base_classifier = DecisionTreeClassifier(max_depth=4) 6 7# Create the bagging classifier 8bagging_classifier = BaggingClassifier(base_classifier, n_estimators=10, bootstrap=True, bootstrap_features=False) 9 10# Train the classifier on the training data 11bagging_classifier.fit(X_train, y_train) 12 13# Make predictions on the test data 14predictions = bagging_classifier.predict(X_test) In this example, we've created a base classifier using a decision tree with a maximum depth of 4. We've then created a bagging classifier that trains 10 decision trees on different versions of the training set (created by sampling with replacement). Finally, we've trained the classifier on the training data and made predictions on the test data.\nBayesian model averaging Bayesian model averaging (BMA) is an ensemble technique that involves training multiple models on the training data, and then using Bayesian techniques to combine the predictions of the individual models.\nOne of the main advantages of BMA is that it can improve the performance of machine learning models by incorporating uncertainty into the model selection process. BMA algorithms are based on the idea that it is possible to train a series of simple models, and then use Bayesian techniques to combine their predictions in a way that takes into account the uncertainty of each model.\nTo understand how BMA works, let's consider a simple example using decision trees and logistic regression. Suppose we have a dataset with 100 samples and 10 features. We can train a decision tree model and a logistic regression model on the data, and then use the predictions of these models to calculate the posterior probability of each class for each sample.\nTo combine the predictions of the two models, we can use Bayesian model averaging to calculate the weighted average of the posterior probabilities, where the weights are determined by the relative performance of the models. For example, if the decision tree model has a higher accuracy than the logistic regression model, it would receive a higher weight in the weighted average.\nHere's some example Python code that demonstrates how to implement BMA using the scikit-learn library:\n1from sklearn.ensemble import BaggingClassifier 2from sklearn.tree import DecisionTreeClassifier 3from sklearn.linear_model import LogisticRegression 4 5# Create the decision tree and logistic regression models 6decision_tree = DecisionTreeClassifier(max_depth=4) 7logistic_regression = LogisticRegression() 8 9# Train the models on the training data 10decision_tree.fit(X_train, y_train) 11logistic_regression.fit(X_train, y_train) 12 13# Make predictions on the test data 14predictions_dt = decision_tree.predict_proba(X_test) 15predictions_lr = logistic_regression.predict_proba(X_test) 16 17# Calculate the weighted average of the posterior probabilities 18weights = [0.7, 0.3] # weights determined by the relative performance of the models 19predictions = weights[0]*predictions_dt + weights Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/bagging-boosting-stacking-blending-bayesian-averaging/","section":"artificial-intelligence","tags":["machine learning","data science"],"title":"Ensemble Techniques in Machine Learning - A Practical Guide to Bagging, Boosting, Stacking, Blending, and Bayesian Model Averaging"},{"body":"","link":"https://blog.sksoumik.com/series/dsa/","section":"series","tags":null,"title":"dsa"},{"body":"Python's collections module implements specialized container datatypes providing alternatives to Python’s general purpose built-in containers, dict, list, set, and tuple.\nThis module has the following containers:\n11. Counter() 22. namedtuple() 33. deque() 44. defaultdict() 55. OrderedDict() 66. UserDict() 77. UserString() 88. UserList() 99. ChainMap() In my experience, out of all of these modules Counter, defaultdict, OrderedDict, and deque are the most useful ones. The following section explains how Counter, defaultdict, OrderedDict, and deque works.\nCounter Time complexity: Constructing it is O(n), because it has to iterate over the input, but operations on individual elements remain O(1)\ncode\n1from collections import Counter 2 3items = [\u0026#39;B\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;C\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;C\u0026#39;] 4counter = Counter(items) 5print(counter) output\n1Counter({\u0026#39;B\u0026#39;: 5, \u0026#39;A\u0026#39;: 3, \u0026#39;C\u0026#39;: 2}) Print all the items with their occurrence numbers\n1for item, count in counter.items(): 2 print(item, count) output\n1B 5 2A 3 3C 2 Find which item is most common in a list\n1items = [\u0026#39;B\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;C\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;C\u0026#39;] 2 3counter = Counter(items) 4print(counter.most_common(1)) # [(\u0026#39;B\u0026#39;, 5)] 5print(counter.most_common(1)[0][0]) # B 6print(counter.most_common(1)[0][1]) # 5 output\n1[(\u0026#39;B\u0026#39;, 5)] 2B 35 We can also pass dictionary in the Counter\n1d = {\u0026#39;A\u0026#39;: 3, \u0026#39;B\u0026#39;: 5, \u0026#39;C\u0026#39;: 2} 2counter = Counter(d) 3print(counter) output\n1Counter({\u0026#39;B\u0026#39;: 5, \u0026#39;A\u0026#39;: 3, \u0026#39;C\u0026#39;: 2}) You can also pass elements and its count directly\n1counter = Counter(A=3, B=5, C=2) 2print(counter) output\n1Counter({\u0026#39;B\u0026#39;: 5, \u0026#39;A\u0026#39;: 3, \u0026#39;C\u0026#39;: 2}) We can also construct all the elements from the above program\n1counter = Counter(A=3, B=5, C=2) 2print(sorted(counter.elements())) output\n1[\u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;C\u0026#39;] We can also add two Counter objects\n1A = Counter(A=3, B=5, C=2) 2B = Counter(A=1, B=2, C=3) 3C = A + B 4print(C) output\n1Counter({\u0026#39;B\u0026#39;: 7, \u0026#39;C\u0026#39;: 5, \u0026#39;A\u0026#39;: 4}) We can also subtract one from another\n1A = Counter(A=3, B=5, C=2) 2B = Counter(A=1, B=2, C=3) 3A.subtract(B) 4print(A) output\n1Counter({\u0026#39;B\u0026#39;: 3, \u0026#39;A\u0026#39;: 2, \u0026#39;C\u0026#39;: -1}) Find the total of all counts\n1c = Counter(A=3, B=5, C=2) 2print(sum(c.values())) # 10 Now, let's see some very basic functions comes with Counter\n1items = [\u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;C\u0026#39;] 2c = Counter(items) 3 4# print all the elements 5print(list(c.elements())) 6# print all the uniqe elements 7print(list(c)) 8# print all the counts 9print(list(c.values())) output\n1[\u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;C\u0026#39;] 2[\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;] 3[3, 5, 2] defaultdict Dict is one of the data structures available in Python which allows data to be stored in the form of key-value pairs.\nExample:\n1d = {\u0026#39;a\u0026#39;: 2, \u0026#39;b\u0026#39;: 5, \u0026#39;c\u0026#39;: 6} Problem with Dictionary Dictionaries work well unless you encounter missing keys. Suppose you are looking for a key-value pair where there is no value in the dictionary - then you might encounter a KeyError problem. Something like this:\n1d = {\u0026#39;a\u0026#39;: 2, \u0026#39;b\u0026#39;: 5, \u0026#39;c\u0026#39;: 6} 2d[\u0026#39;z\u0026#39;] # z is not present in dict so it will throw a error You will see something like this:\n1Traceback (most recent call last): 2 File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 2, in \u0026lt;module\u0026gt; 3 d[\u0026#39;z\u0026#39;] 4KeyError: \u0026#39;z\u0026#39; To overcome the above problem we can use different ways:\nusing get using defaultdict from collection module. Using get , if the value doesn't exist for a certain key, it prints None\n1d = {\u0026#39;a\u0026#39;: 2, \u0026#39;b\u0026#39;: 5, \u0026#39;c\u0026#39;: 6} 2 3print(d.get(\u0026#39;b\u0026#39;)) # 5 4print(d.get(\u0026#39;d\u0026#39;)) # None Using defaultdict with int\n1from collections import defaultdict 2 3d = defaultdict(int) 4d[\u0026#39;a\u0026#39;] = 1 5d[\u0026#39;b\u0026#39;] = 2 6d[\u0026#39;c\u0026#39;] = 3 7 8print(d[\u0026#39;a\u0026#39;]) # 1 9print(d[\u0026#39;d\u0026#39;]) # 0 int: default will be an integer value of 0 str: default will be an empty string \u0026quot;\u0026quot; list: default will be an empty list [] 1d = defaultdict(list) 2d[\u0026#39;a\u0026#39;].append(1) 3d[\u0026#39;a\u0026#39;].append(2) 4 5d[\u0026#39;c\u0026#39;].append(3) 6d[\u0026#39;c\u0026#39;].append(4) 7 8print(d[\u0026#39;a\u0026#39;]) # [1, 2] 9print(d[\u0026#39;c\u0026#39;]) # [3, 4] 10print(d[\u0026#39;d\u0026#39;]) # [] We can also set custom value for the default option\n1d = defaultdict(lambda: \u0026#39;Custom\u0026#39;) 2d[\u0026#39;a\u0026#39;] = 1 3d[\u0026#39;b\u0026#39;] = 2 4 5print(d[\u0026#39;a\u0026#39;]) # 1 6print(d[\u0026#39;b\u0026#39;]) # 2 7print(d[\u0026#39;c\u0026#39;]) # Custom We can also convert a normal dictionary to a defaultdict\n1normal_dict = {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2, \u0026#39;c\u0026#39;: 3} 2 3# make dc as a defaultdict 4d = defaultdict(int, normal_dict) 5print(d[\u0026#39;a\u0026#39;]) # 1 6print(d[\u0026#39;d\u0026#39;]) # 0 Print the keys and values of a dictionary\n1normal_dict = {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2} 2 3d = defaultdict(int, normal_dict) 4 5for k, v in d.items(): 6 print(k, v) output\n1a 1 2b 2 OrderedDict The only difference between OrderedDict and dict is that, in OrderedDict, it maintains the orders of keys as inserted. In the dict, the ordering may or may not happen.\ndeque Python’s collections module provides a class called deque that’s specially designed to provide fast and memory-efficient ways to append and pop item from both ends of the underlying data structure.\nDeque is preferred over a list in the cases where we need quicker append and pop operations from both ends of the container, as deque provides an O(1) time complexity for append and pop operations as compared to a list that provides O(n) time complexity.\n1from collections import deque 2 3d = deque() 4d.append(1) 5# add item to the right 6d.append(2) 7d.append(3) 8 9print(d) # deque([1, 2, 3]) print all values of deque:\n1for num in d: 2 print(num) output\n11 22 33 Add item to the left\n1d.appendleft(4) 2print(d) # deque([4, 1, 2, 3]) We can also pass different iterable to the deque.\n1nums = [1, 2, 3, 4, 5] 2d = deque(nums) 3print(d) # deque([1, 2, 3, 4, 5]) 4 5strs = \u0026#34;abcde\u0026#34; 6d = deque(strs) 7print(d) # deque([\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;]) Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/tutorial-on-python-collections-module/","section":"software-engineering","tags":["python","programming"],"title":"Python Collections Module Tutorial"},{"body":"Decision Tree, Random Forest (RF), and Gradient Boosting (GB) are three popular algorithms used for supervised learning tasks such as classification and regression. In this blog, we will compare these three algorithms in terms of their features, performance, and usability.\nDecision Tree is a simple and intuitive algorithm that can be used for classification and regression tasks. A Decision Tree model is built by recursively partitioning the training data into smaller and smaller subsets based on the values of the input features. The resulting tree structure provides a clear and interpretable representation of the underlying data, and can be used to make predictions on new data.\nRandom Forest is an ensemble learning algorithm that builds multiple Decision Tree models and combines their predictions to improve the overall accuracy and stability of the model. In a Random Forest model, each Decision Tree is trained on a different subset of the training data, and the predictions of the individual trees are combined using a weighted average or majority voting. This allows the Random Forest model to capture more of the underlying complexity and variability of the data, and to make more accurate and reliable predictions.\nGradient Boosting is an ensemble learning algorithm that builds multiple weak learners (such as Decision Tree models) and combines them to form a strong learner that can make accurate predictions. In Gradient Boosting, the individual weak learners are trained in sequence, and each successive learner focuses on the mistakes made by the previous learners. This allows Gradient Boosting to learn a highly non-linear and complex decision boundary, and to make highly accurate predictions.\nOne key difference between Decision Tree, Random Forest, and Gradient Boosting is the way in which the model is built and used. A Decision Tree model is built by recursively partitioning the training data into smaller and smaller subsets, while a Random Forest model is built by training multiple Decision Tree models on different subsets of the data and combining their predictions. Gradient Boosting, on the other hand, builds multiple weak learners in sequence and combines them to form a strong learner. This makes Gradient Boosting more complex and computationally expensive than Decision Tree and Random Forest, but also more accurate and robust.\nAnother key difference between Decision Tree, Random Forest, and Gradient Boosting is the way in which the predictions are made. A Decision Tree model makes predictions by traversing the tree structure and applying a set of rules or thresholds at each node, while a Random Forest model makes predictions by combining the predictions of multiple Decision Tree models. Gradient Boosting, on the other hand, makes predictions by combining the predictions of multiple weak learners. This makes Gradient Boosting more stable and less sensitive to noise and outliers in the data, but also less interpretable than Decision Tree and Random Forest.\nWhen to use which one? The choice between Decision Tree, Random Forest, and Gradient Boosting will depend on the specific requirements and characteristics of the dataset and the application. Here are some general guidelines for choosing which algorithm to use:\nIf interpretability and simplicity are the primary concerns, then Decision Tree may be the best choice. Decision Tree provides a clear and interpretable representation of the underlying data, and is easy to use and understand. If accuracy and stability are the primary concerns, then Random Forest or Gradient Boosting may be the best choice. Both Random Forest and Gradient Boosting are ensemble learning algorithms that are able to capture more of the underlying complexity and variability of the data, and are less sensitive to noise and outliers in the data. If the dataset is large and complex, and computational efficiency is a concern, then Random Forest may be the best choice. Random Forest is more efficient and scalable than Gradient Boosting, and can handle large and complex datasets more efficiently. If the dataset is small or medium-sized, and the goal is to build a highly accurate and robust model, then Gradient Boosting may be the best choice. Gradient Boosting is able to learn a highly non-linear and complex decision boundary, and can make highly accurate predictions on small or medium-sized datasets. Ultimately, the choice of algorithm will depend on the specific requirements and characteristics of the dataset and the application. The best algorithm can be determined through experimentation and cross-validation.\nKey Points Boosting (which is sequential), RF grows trees in parallel.\nRF and GB Both uses decision trees. But Unlike random forests, the decision trees in gradient boosting are built additively; in other words, each decision tree is built one after another.\nIn random forests, the results of decision trees are aggregated at the end of the process. Gradient boosting doesn’t do this and instead aggregates the results of each decision tree along the way to calculate the final result.\nBoosting reduces error mainly by reducing bias. RF reduces error mainly by reducing variance.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/comparing-random-forest-decision-tree-gradient-boosting/","section":"artificial-intelligence","tags":["machine learning","algorithm","data science"],"title":"Understanding the Differences between Decision Tree, Random Forest, and Gradient Boosting"},{"body":"Start With A Simple Architecture To begin building an app, we will start from the beginning. We will create a basic app with some users. The easiest way to do this is to put the whole app on one server. This is a common way to start. The app and any API's will run on a server like Apache or Tomcat. We will also use a database like Oracle or MySQL.\nThe way we have our app set up now has some problems. If the database stops working, the whole system stops working. If the server that runs the app stops working, the whole system stops working too. This means that if one part of the system breaks, everything breaks and we don't have a backup plan.\nScalability Our system might need to handle more things, like more data or more users. To do that, we need it to be able to handle more things without making it worse for the user. This is called scalability. We can make our system handle more things by adding more resources. There are two ways to do this: scale-up or vertical scalingand scale-out or horizontal scaling. We have to decide which one to use.\nVertical Scaling Vertical scaling means making our system stronger by adding more resources to it, like more memory or a faster processor. This can be done by upgrading the server's hardware like adding more RAM, hard drives, or network interfaces. But this can be limited by the server's operating system and the cost of the new hardware. Also, it requires shutting down the server to do the upgrade, which can cause downtime. Additionally, Scaling up also can be done by optimizing the code and queries to run faster.\nOn the other hand, Scaling down means removing resources from the server like CPU, memory, and disks.\nHorizontal Scaling Horizontal scaling means adding more things, like more servers, to handle more users or data. It's harder to do this than vertical scaling because it has to be planned for before building the system. It may cost more at first but it will be worth it in the long run.\nWe also need to think about the cost of maintaining more servers and how the code needs to be changed to work with multiple servers.\nAdd Load Balancer A load balancer is a device or software that helps distribute incoming network traffic across multiple servers or resources. The primary purpose of a load balancer is to increase the availability and scalability of a network service by distributing the workload across multiple servers.\nA load balancer can be a hardware device, such as a dedicated appliance or a software that runs on a general-purpose server, that uses a variety of algorithms to distribute incoming requests to multiple servers or resources.\nBy distributing the traffic across multiple servers, a load balancer can help ensure that no single server is overwhelmed by too many requests, which can improve the overall responsiveness and availability of a network service. Additionally, load balancer can also improve security by directing traffic to the appropriate resources, and can provide other features like SSL offloading, DDoS protection, and health checking of the servers.\nA load balancer is typically placed in between the client and the server to evenly distribute incoming traffic to multiple backend servers using different methods. This tool can be used in different locations, such as between web servers and database servers or between the client and the web servers.\nHAProxy and NGINX are two common open-source load balancing software options.\nWhen the traffic on a website increases, more servers can be added to the mix and the load balancer will take care of routing the traffic to the right place. There are different ways a load balancer can distribute traffic, such as:\nRound robin: sending requests to each server in a sequential order Least number of connections: sending requests to the server with the least connections Fastest response time: sending requests to the server with the fastest response time Weighted: giving more requests to stronger servers IP Hash: using a calculation based on the client's IP address to send the request to a specific server Load balancing can also be done using a hardware appliance or a software alternative. Hardware appliances can make changes to the servers instantly, while software load balancing can work at both the network and application layers.\nAt the network layer (layer 4), the load balancer uses information from the TCP protocol to select a server without considering the specifics of the request. At the application layer (layer 7), the load balancer can use information from the request, such as the query string or cookies, to make its decision.\nDatabase Scaling Using a relational database management system (RDBMS) like Oracle or MySQL is an easy way to store data. However, as the amount of data grows, these systems can become difficult to manage.\nThere are various methods for scaling relational databases, such as:\nMaster-slave replication: This is a way to distribute data across multiple servers, where one server is designated as the \u0026quot;master\u0026quot; and the others are designated as \u0026quot;slaves.\u0026quot; The master server is responsible for handling all the updates and changes to the data, while the slave servers act as backups. If the master goes down, one of the slaves can take its place. This method helps keep the data safe and improve performance.\nMaster-master replication: This is a similar method to master-slave replication, but in this case, multiple servers can act as both the master and slave. This means that any of the servers can handle updates and changes to the data, and any of the servers can act as a backup. This method is used to improve performance and reliability.\nFederation: This method is used to break up the data into smaller chunks and distribute it among multiple servers. This makes it easier to manage the data and improves performance as the data grows.\nSharding: Sharding is a method of splitting a database into multiple smaller parts, called \u0026quot;shards.\u0026quot; Each shard is a smaller version of the original database and contains a specific subset of the data. Each shard is then stored on a different server. This helps to distribute the load and improve performance as the data grows. The database is split into shards by a specific key, such as a user ID, and each shard is responsible for a specific range of the keys. For example, one shard might be responsible for all the user IDs between 1 and 10,000, while another shard might be responsible for all the user IDs between 10,001 and 20,000. This way, when a user requests data, it will be directed to the shard that holds the user's data and returns the result much faster.\nDenormalization: This method is used to make changes to the database structure to improve performance. This can include adding extra columns or tables, or making changes to the way data is stored.\nSQL tuning: This method is used to optimize the SQL queries used to access the data. This can include making changes to the queries, or adding indexes to the database to make it faster.\nMaster-Slave Replication Master-slave replication is a method of scaling a database by creating multiple copies of the same data and distributing them across different servers. This allows for increased performance and availability, as well as the ability to handle increased traffic and load.\nIn master-slave replication, one server, called the master, acts as the primary source of data. All write operations, such as inserting, updating, and deleting data, are performed on the master. The master then replicates these changes to one or more slave servers, which act as read-only copies of the master's data.\nWhen a change is made to the master, it is written to the binary log, a special file that contains a record of all changes made to the master. The slave servers then connect to the master and retrieve the changes from the binary log, applying them to their own copy of the data.\nThere are several benefits to using master-slave replication for database scaling:\nIncreased performance: By distributing read operations across multiple slave servers, the load on the master is reduced, allowing for faster read performance. High availability: If the master server goes down, one of the slaves can be promoted to take its place, minimizing downtime. Easy scalability: Additional slave servers can be added as needed to handle increased traffic and load. There are also some potential drawbacks to master-slave replication:\nIncreased complexity: Setting up and maintaining multiple servers can be more complex than managing a single server. Data inconsistencies: In some cases, data on the slaves may not be an exact copy of the data on the master, due to replication lag or other factors. Limited write performance: Since all writes must be performed on the master, write performance may be limited. Master-Master Replication In Master-Master replication, there are two or more servers that act as Masters, each with the ability to read and write to the database. Each server maintains its own set of binary logs, and they communicate with each other to synchronize their data. When a change is made to one Master, it is written to its binary log, and then the other Master server retrieves the changes from the binary log and applies them to its own copy of the data.\nThere are several benefits to using Master-Master replication for database scaling:\nIncreased performance: By distributing read and write operations across multiple Master servers, the load on any one server is reduced, allowing for faster performance. High availability: If one of the Master servers goes down, the other Master can continue to handle requests, minimizing downtime. Improved scalability: Additional Master servers can be added as needed to handle increased traffic and load. However, there are also some potential drawbacks to Master-Master replication:\nIncreased complexity: Setting up and maintaining multiple Master servers can be more complex than managing a single Master. Data inconsistencies: In some cases, data on the Master servers may not be an exact copy of each other, due to replication lag or other factors. Conflict Resolution: Master-Master replication can lead to conflicts when the same data is updated on multiple Masters simultaneously. This requires a conflict resolution mechanism to be in place. ","link":"https://blog.sksoumik.com/artificial-intelligence/designing-large-scale-high-performance-software-systems/","section":"artificial-intelligence","tags":["software engineering","system design"],"title":"How to design a large scale software system that supports millions of users."},{"body":"Security Linux tends to be a highly reliable and secure system than any other operating system (OS). Linux and Unix-based OS have fewer security flaws, as many developers constantly review the code. And anyone has access to its source code. So, you won’t need any anti-virus software to protect your PC from malware and viruses. The reason it’s secure is that it’s open-source, which means you can see its source code. As a result, bugs in the Linux OS will fix rapidly compared to other OS.\nFree If you use windows, you need to buy or use a cracked version, like a thief. It just doesn’t feel good after a certain age when you use something unethically when a free better OS is available for you.\nDeveloper Friendly The package manager of Linux is way more robust than any other OS. Installing software in Linux is notably easy compared to Windows. It can enhance the workflow for programmers remarkably. In most cases, you only need to open up the terminal and write:\n1sudo apt-get install \u0026lt;software-name\u0026gt; That’s because Linux has software managers like apt, rpm, dpkg, and synaptic. In Windows, you need to search for the website where you can find it. Download the .exe file. Then click on the .exe file. Click, click, click, ……. Finally, in most cases, you need to reboot the system to make the software work. Whereas in Linux, this process is way easier and requires no rebooting to make the software work.\nPre-installed Powerful Tools A lot of handy useful programming tools comes pre-installed with Linux. For example, grep, wget, cron, etc. Linux also comes with native support for SSH, which helps manage servers swiftly.\nIt also depends on the different distributions of Linux. Like in Kali Linux, you get many pre-installed hacking and penetration testing tools, which helps a beginner start the learning process immediately.\nSystem Update In Linux, users have complete control over updating their systems. The system updates are immensely faster in Linux. You can update the system anytime or never; that’s your wish. On the other hand, Windows sometimes forces the user to update the system.\nYou power up your system sometimes, and out of nowhere, the system starts updating when you may need to send an urgent mail to someone. Also, system updates are notoriously slow in Windows.\nPrivacy Windows always collect user data. Almost everything. Even if they give you the option to diagnostics data viewer, many complain that it’s just a pretense. Linux doesn’t have any hide and seeks games, as you can always look into its source code.\nWhen it comes to Linux, you can see everything transmitting from your system if you’re tech-savvy enough. Even if you’re not, tens of thousands of eyes are looking into the source code and looking for flaws that can cause any vulnerability to the system.\nTask Automation One can work more efficiently by automating tasks, making life easier. Bash shell scripting doesn’t come native to windows, and you need to install third-party software to make it work in an ugly way. Experienced Linux users don’t write one thing twice because Linux lends itself very well to task automation.\nSometimes we do the same thing repeatedly, which consumes a lot of time. All of these repetitive tasks can be automated with one-liners. Linux terminal is the heart of Linux. There are Bash, SH, Korn, ZSH, and Fish shells; use whatever you like. The terminals are about efficiency—no unnecessary movements, browsing through file manager, and clicks.\nPortability Linux is a portable OS that supports a wide range of computer architectures. Portability means quickly moving your code from one system architecture to another. Having a lightweight system is amazing when you need to troubleshoot a system in daily life.\nNow, let’s say you have a GitHub repo where there is a set of instructions to install all the software you use in just one file. You can execute that one file from anywhere to make your system ready to go with all the software you use in an entirely new Linux installed in your system. Perhaps, within 20 minutes, your system will be prepared to go as your daily driver.\nCustomization If you don’t like something in Linux, you can remove it or modify it according to your choice. For example, if you don’t like GNOME, you can change it to KDE plasma. Else, you can also install a lot of extensions to enhance your desktop environment, or even you can make your custom UI. You won’t find this level of freedom in any other OS.\nYou can even run your Linux without any UI at all. A lot of people/companies use Linux only to maintain servers, so all you need is just a terminal.\nHardware Support Windows-powered systems require higher hardware requirements. As the OS evolves, your old hardware-supported PC may become obsolete with the latest Windows systems because it won’t support your old hardware. Whereas with Linux, you can install it on very low-end hardware systems.\nSystem Performance Linux-powered PCs are way faster than Windows. The main reason for that Linux is a lightweight system, and Windows is crammed with lots of unnecessary software. A lot of systems running in the background make your windows PCs sluggish.\nAnother reason is file systems are pretty organized in Linux, and files are located in chunks closer to each other, making read-write operation way faster than Windows.\nThat’s the reason most cloud systems run on Linux. Even Microsoft runs Linux to run Azure. xD\nLinux Can Run Almost Anywhere You can run Linux from Super Computers to Game Consoles, Smart TVs, Smartwatches, Car infotainment systems, Flight entertainment systems, Self-driving cars, Nuclear Submarines, and many more. NASA heavily relies on Linux for data transmission from satellites and telescopes. Read about this more here.\nMeaningful Error Message “Oops! Something went wrong…” type of message is not useful at all. I know that Windows users are tired of this blue screen.\nThis sort of error message doesn’t help in any way. As a user, I want to know what went wrong. If you can understand the explicit error message, you can at least attempt to solve the problem.\nLinux provides detailed error logs that tell you what caused the error. Knowing what went wrong precisely gives you the option to search for the solution on the internet.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/benefits-of-linux-over-windows/","section":"software-engineering","tags":["linux","operating system"],"title":"13 Reasons Why Linux Is Better Than Windows"},{"body":"","link":"https://blog.sksoumik.com/tags/operating-system/","section":"tags","tags":null,"title":"operating system"},{"body":"Word embedding is a technique in natural language processing (NLP) where words are represented as vectors of real numbers. This allows words with similar meanings to have similar representation, and can be used in various NLP tasks such as machine translation and text classification.\nThere are several different techniques for word embedding in natural language processing (NLP), including:\nTF-IDF — Term Frequency-Inverse Document Frequency TF-IDF (Term Frequency-Inverse Document Frequency) is a technique used in natural language processing to measure the importance of a word in a document. It is typically used to improve the performance of text classification and other NLP tasks by weighting words based on their importance in the document.\nThe basic idea behind TF-IDF is that words that occur frequently in a document are important, but common words that occur in many documents are not. The term frequency (TF) measures the number of times a word appears in a document, while the inverse document frequency (IDF) measures how common a word is across all documents. The product of these two values is the TF-IDF score for a word in a document.\nHere is an example of how to implement the TF-IDF technique in Python:\n1# Import the TfidfVectorizer class from scikit-learn 2from sklearn.feature_extraction.text import TfidfVectorizer 3 4# Load the text corpus 5corpus = [ 6 \u0026#39;This is the first document.\u0026#39;, 7 \u0026#39;This document is the second document.\u0026#39;, 8 \u0026#39;And this is the third one.\u0026#39;, 9 \u0026#39;Is this the first document?\u0026#39; 10] 11 12# Create a TfidfVectorizer instance 13vectorizer = TfidfVectorizer() 14 15# Fit the vectorizer on the text corpus 16vectorizer.fit(corpus) 17 18# Transform the text corpus into a TF-IDF matrix 19X = vectorizer.transform(corpus) 20 21# Print the resulting matrix 22print(X.toarray()) This code will create a TF-IDF matrix for the given text corpus, where each row corresponds to a document and each column corresponds to a word. The elements of the matrix are the TF-IDF scores for the words in the corresponding document.\nYou can also use the vectorizer to obtain the TF-IDF vectors for individual documents in the corpus. For example, to get the TF-IDF vector for the first document in the corpus, you can do the following:\n1# Get the TF-IDF vector for the first document in the corpus 2vector = vectorizer.transform([corpus[0]]) 3 4# Print the resulting vector 5print(vector.toarray()) This code will print the TF-IDF vector for the first document in the corpus, where each element is the TF-IDF score for the corresponding word in the document.\nWord2Vec Word2vec is a technique for natural language processing that uses the frequency of words in a corpus to create word vectors. It has two main training algorithms: continuous bag-of-words (CBOW) and skip-gram.\nThe CBOW model predicts the current word based on its surrounding context, while the skip-gram model predicts the surrounding context words based on the current word. In general, the skip-gram model performs better for smaller datasets, while the CBOW model performs better for larger datasets.\nHere is an example of how to use the word2vec algorithm to train word vectors on a corpus of text using the CBOW model in Python:\n1# Import the word2vec module from gensim 2from gensim.models import word2vec 3 4# Load the text corpus 5corpus = open(\u0026#39;text_corpus.txt\u0026#39;).read() 6 7# Train the word2vec model 8model = word2vec.Word2Vec(corpus, size=100, window=5, min_count=5, workers=4, sg=0) 9 10# Save the trained model 11model.save(\u0026#39;word2vec_model\u0026#39;) Once the model is trained, you can use it to obtain the word vectors for any word in the corpus. For example, to get the vector for the word \u0026quot;apple\u0026quot;, you can do the following:\n1# Load the trained model 2model = word2vec.Word2Vec.load(\u0026#39;word2vec_model\u0026#39;) 3 4# Get the vector for the word \u0026#34;apple\u0026#34; 5vector = model[\u0026#39;apple\u0026#39;] GloVe Glove is a specific method for creating word embeddings that has been shown to perform well on a wide range of NLP tasks. The key idea behind Glove is to create word vectors that are able to capture the co-occurrence statistics of words in a corpus. This is done by training the algorithm to predict the co-occurrence counts of words, rather than just their surrounding words.\nOne of the advantages of Glove is that it is able to create high-quality word vectors from a relatively small corpus of text. This makes it particularly useful for applications that require word embeddings for a specific domain, such as medical text or legal documents.\nAnother advantage of Glove is that it is able to incorporate both global and local information into the word vectors. Global information refers to the overall co-occurrence statistics of words in the corpus, while local information refers to the specific context in which a word appears. This makes the Glove vectors more versatile than some other word embedding methods, which only capture global information.\nTo implement Glove in Python, we will use the Gensim library. Gensim is a powerful open-source library for topic modelling and natural language processing, which includes implementations of several popular word embedding algorithms, including Glove.\nOnce Gensim is installed, you can create a Glove model by using the Glove class from the gensim.models.word2vec module. The Glove class takes several arguments, including the corpus, size, window, alpha, and min_alpha arguments that specify the corpus of text to be used for training, the dimensionality of the word vectors, the context window size, the initial learning rate, and the minimum learning rate, respectively.\nHere is an example of how to create a Glove model using the Glove class:\n1from gensim.models.word2vec import Glove 2 3# Create a list of sentences, where each sentence is a list of words 4corpus = [[\u0026#34;cat\u0026#34;, \u0026#34;dog\u0026#34;, \u0026#34;bird\u0026#34;], [\u0026#34;mouse\u0026#34;, \u0026#34;rat\u0026#34;, \u0026#34;hamster\u0026#34;]] 5 6# Create a Glove model with a vector size of 100 and a context window size of 5 7model = Glove(corpus, size=100, window=5, alpha=0.05, min_alpha=0.0001) 8 9# Train the model on the corpus 10model.train(corpus, total_examples=len(corpus), epochs=10) Once the model is trained, you can access the word vectors using the model.wv attribute, which contains a Word2VecKeyedVectors object. You can use this object to access the word vectors for individual words using the word_vec() method. For example:\n1# Get the word vector for the word \u0026#34;cat\u0026#34; 2cat_vector = model.wv.word_vec(\u0026#34;cat\u0026#34;) You can also perform vector operations on the word vectors, such as vector addition and vector similarity calculations. For example:\n1# Add the vectors for the words \u0026#34;cat\u0026#34; and \u0026#34;dog\u0026#34; 2cat_dog_vector = model.wv.word_vec(\u0026#34;cat\u0026#34;) + model.wv.word_vec(\u0026#34;dog\u0026#34;) 3 4# Calculate the cosine similarity between the vectors for the words \u0026#34;cat\u0026#34; and \u0026#34;dog\u0026#34; 5similarity = model.wv.similarity(\u0026#34;cat\u0026#34;, \u0026#34;dog\u0026#34;) BERT Embeddings BERT (Bidirectional Encoder Representations from Transformers) is a state-of-the-art natural language processing (NLP) model that can be used to create word embeddings. BERT word embeddings are vector representations of words that are trained using a large corpus of text. Unlike some other word embedding methods, BERT takes into account the context in which words appear, which allows it to capture the meaning of words in a more nuanced and accurate way.\nBERT embeddings are created by training a BERT model on a large corpus of text. The BERT model is a type of transformer network that uses attention mechanisms to learn the relationships between words in a sentence. This allows BERT to capture the context in which words appear and to create word embeddings that accurately reflect the meaning of words in the context of the sentence.\nTo implement BERT embeddings using the huggingface library in Python, you would first need to install the library by running the following command:\n1pip install transformers Once the library is installed, you can use the AutoTokenizer and AutoModel classes from the transformers library to create a BERT model and generate BERT embeddings for a list of words.\nHere is an example of how to use the AutoTokenizer and AutoModel classes to generate BERT embeddings for a list of words:\n1# Import the AutoTokenizer and AutoModel classes 2from transformers import AutoTokenizer, AutoModel 3 4# Create a BERT tokenizer 5tokenizer = AutoTokenizer.from_pretrained(\u0026#34;bert-base-uncased\u0026#34;) 6 7# Create a BERT model 8model = AutoModel.from_pretrained(\u0026#34;bert-base-uncased\u0026#34;) 9 10# Create a list of words 11words = [\u0026#34;cat\u0026#34;, \u0026#34;dog\u0026#34;, \u0026#34;bird\u0026#34;] 12 13# Tokenize the words 14tokens = tokenizer.tokenize(words) 15 16# Convert the tokens to BERT input format 17inputs = tokenizer.encode(tokens, return_tensors=\u0026#34;pt\u0026#34;) 18 19# Use the BERT model to generate BERT embeddings for the tokens 20outputs = model(**inputs) Once the BERT embeddings are generated, you can access them using the outputs variable. You can also use the tokenizer and model objects to perform other operations on the BERT embeddings, such as calculating the cosine similarity between two words.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/understanding-word-embeddings/","section":"artificial-intelligence","tags":["machine learning","NLP","deep learning"],"title":"Different Word Embedding Techniques for Text Analysis"},{"body":"Lambda The Lambda function, also known as an anonymous or inline function, is a way to create a function without giving it a name. This can be useful when you need to define a function that will only be used once, or when you want to pass a function as an argument to another function.\nHere is an example of using a Lambda function in Python:\n1# Define a Lambda function that takes two arguments and returns their sum 2sum_func = lambda x, y: x + y 3 4# Call the Lambda function 5result = sum_func(1, 2) # Returns 3 Map The Map function in Python applies a function to each element in a sequence of data. This can be useful for transforming the elements in a sequence, such as converting a list of strings to a list of integers or a list of tuples to a list of lists.\nHere is an example of using the Map function in Python:\n1# Define a function that converts a string to an integer 2def string_to_int(s): 3 return int(s) 4 5# Define a list of strings 6strings = [\u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;3\u0026#39;, \u0026#39;4\u0026#39;, \u0026#39;5\u0026#39;] 7 8# Apply the string_to_int function to each element in the list using Map 9integers = list(map(string_to_int, strings)) # Returns [1, 2, 3, 4, 5] In this example, we first define a function that takes a string as input and returns the corresponding integer value. Then we define a list of strings and use the map function to apply the string_to_int function to each element in the list. The map function returns a generator object, which we can convert to a list using the list function.\nThe Map function can also be used with Lambda functions to create a more concise and expressive syntax. Here is an example of using a Lambda function with Map:\n1# Define a list of strings 2strings = [\u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;3\u0026#39;, \u0026#39;4\u0026#39;, \u0026#39;5\u0026#39;] 3 4# Use Map and a Lambda function to convert the strings to integers 5integers = list(map(lambda s: int(s), strings)) # Returns [1, 2, 3, 4, 5] In this example, we use the map function to apply a Lambda function to each element in the list of strings. The Lambda function takes a string as input and returns the corresponding integer value. The map function returns a generator object, which we can convert to a list using the list function.\nFilter The Filter function in Python is used to select elements from a sequence of data based on a certain criterion. This can be useful for selecting only the elements that meet a certain condition, such as selecting only the even numbers from a list of integers.\nHere is an example of using the Filter function in Python:\n1# Define a function that checks if a number is even 2def is_even(n): 3 return n % 2 == 0 4 5# Define a list of numbers 6numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 7 8# Use Filter and the is_even function to select the even numbers from the list 9even_numbers = list(filter(is_even, numbers)) # Returns [2, 4, 6, 8, 10] In this example, we first define a function that takes a number as input and returns True if the number is even and False otherwise. Then we define a list of numbers and use the filter function to apply the is_even function to each element in the list. The filter function returns a generator object that contains only the elements for which the is_even function returned True. We can convert the generator object to a list using the list function.\nThe Filter function can also be used with Lambda functions to create a more concise and expressive syntax. Here is an example of using a Lambda function with Filter:\n1# Define a list of numbers 2numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 3 4# Use Filter and a Lambda function to select the even numbers from the list 5even_numbers = list(filter(lambda n: n % 2 == 0, numbers)) # Returns [2, 4, 6, 8, 10] In this example, we use the filter function to apply a Lambda function to each element in the list of numbers. The Lambda function takes a number as input and returns True if the number is even and False otherwise. The filter function returns a generator object that contains only the elements for which the Lambda function returned True. We can convert the generator object to a list using the list function.\nReduce The Reduce function in Python is used to apply a function to a sequence of data and reduce the sequence to a single value. This can be useful for combining the elements in a sequence in some way, such as computing the sum or product of a list of numbers.\nHere is an example of using the Reduce function in Python:\n1from functools import reduce 2 3# Define a function that computes the product of two numbers 4def product(x, y): 5 return x * y 6 7# Define a list of numbers 8numbers = [1, 2, 3, 4, 5] 9 10# Use Reduce and the product function to compute the product of all the numbers in the list 11result = reduce(product, numbers) # Returns 120 In this example, we first define a function that takes two numbers as input and returns their product. Then we define a list of numbers and use the reduce function to apply the product function to each element in the list. The reduce function starts by applying the product function to the first two elements in the list, then applies the product function to the result of that operation and the next element in the list, and so on, until all the elements in the list have been combined. The final result is a single value that is the result of combining all the elements in the list using the product function.\nThe Reduce function can also be used with Lambda functions to create a more concise and expressive syntax. Here is an example of using a Lambda function with Reduce:\n1# Define a list of numbers 2numbers = [1, 2, 3, 4, 5] 3 4# Use Reduce and a Lambda function to compute the product of all the numbers in the list 5result = reduce(lambda x, y: x * y, numbers) # Returns 120 In this example, we use the reduce function to apply a Lambda function to each element in the list of numbers. The Lambda function takes two numbers as input and returns their product. The reduce function starts by applying the Lambda function to the first two elements in the list, then applies the Lambda function to the result of that operation and the next element in the list, and so on, until all the elements in the list have been combined. The final result is a single value that is the result of combining all the elements in the list using the Lambda function.\nUsing Lambda, Map, Filter, and Reduce All Together Here is an example of using the Lambda, Map, Filter, and Reduce functions together in Python:\n1from functools import reduce 2 3strings = [\u0026#39;1\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;3\u0026#39;, \u0026#39;4\u0026#39;, \u0026#39;5\u0026#39;] 4 5# Use Map and a Lambda function to convert the strings to integers 6integers = list(map(lambda s: int(s), strings)) # Returns [1, 2, 3, 4, 5] 7 8# Use Filter and a Lambda function to select only the even numbers 9even_numbers = list(filter(lambda n: n % 2 == 0, integers)) # Returns [2, 4] 10 11# Use Reduce and a Lambda function to compute the sum of the even numbers 12result = reduce(lambda x, y: x + y, even_numbers) Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/lambda-map-filter-reduce-python/","section":"software-engineering","tags":["programming","python"],"title":"Lambda, Map, Filter, and Reduce in Python"},{"body":"Recurrent Neural Network A recurrent neural network (RNN), is a type of neural network that can process sequential data, like text, audio, or time series data.\nHere's how it works: first, the RNN takes in some input data, which could be a word in a sentence, a sound wave from an audio recording, or a measurement from a sensor at a specific time. Then, the RNN processes this input and generates an output, which could be a predicted next word in a sentence, a generated audio waveform, or a predicted sensor measurement.\nBut here's the cool part: the RNN also has a \u0026quot;memory\u0026quot; that it can use to remember important information from the input it has seen so far. This allows the RNN to make better predictions because it can take into account not only the current input, but also the context of the inputs it has seen before.\nFor example, if the RNN is processing a sentence, it can use its memory to remember the previous words in the sentence, which can help it predict the next word more accurately. Or if the RNN is processing audio data, it can use its memory to remember the previous sound waves, which can help it generate more realistic-sounding audio.\nThe Architecture of RNN The architecture of a RNN is similar to that of a traditional neural network, which has an input layer, hidden layers, and an output layer. But the key difference is that a RNN also has connections that loop back from the output of the hidden layers to the input of the hidden layers.\nWhen the RNN receives some input data, it processes the data in the input layer and then passes it through the hidden layers. As the data passes through the hidden layers, the RNN uses the looping connections to incorporate information from the previous outputs of the hidden layers into the current inputs. This allows the RNN to build up a \u0026quot;memory\u0026quot; of the inputs it has seen so far, which can help it make better predictions.\nAfter the data has passed through the hidden layers, the RNN generates an output in the output layer, which could be a predicted next word in a sentence, a generated audio waveform, or a predicted sensor measurement. The output of the RNN is then fed back into the input of the hidden layers, which allows the RNN to incorporate this output into its memory and make better predictions on the next step.\nDifference Between A Normal Neural Network And RNN The main difference between a traditional neural network and a RNN is that a traditional neural network processes input data independently, while a RNN processes input data sequentially and maintains a \u0026quot;memory\u0026quot; of the data it has seen so far. This allows a RNN to make use of the order and context of the input data to make better predictions.\nHere is an example of how you might define a traditional neural network using Keras:\n1model = tf.keras.Sequential() 2 3# Add an input layer, which expects input with shape (batch_size, input_length). 4model.add(tf.keras.layers.InputLayer(input_shape=(batch_size, input_length))) 5 6# Add a dense layer with 32 units and a ReLU activation function. 7model.add(tf.keras.layers.Dense(units=32, activation=\u0026#39;relu\u0026#39;)) 8 9# Add a dense layer with 10 units and a softmax activation function. 10model.add(tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;)) This code defines a Sequential model with two dense layers. The input layer specifies the shape of the input data, and the dense layers process the input data and generate an output. The first dense layer has 32 units and uses the relu activation function, which means it will only pass on non-negative values. The second dense layer has 10 units and uses the softmax activation function, which ensures that the output of the layer is a probability distribution over the 10 classes.\nHere is an example of how you might define a RNN using Keras:\n1model = tf.keras.Sequential() 2 3# Add an input layer, which expects input with shape (batch_size, input_length, input_dim). 4model.add(tf.keras.layers.InputLayer(input_shape=(batch_size, input_length, input_dim))) 5 6# Add a simple RNN layer with 32 units. 7model.add(tf.keras.layers.SimpleRNN(units=32)) 8 9# Add a dense layer with 10 units and a softmax activation function. 10model.add(tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;)) This code defines a Sequential model with a SimpleRNN layer and a dense layer. The input layer specifies the shape of the input data, which in this case is three-dimensional (batch_size, input_length, input_dim). The SimpleRNN layer processes this input data and generates an output, which is then passed to the dense layer to map to the desired number of classes.\nOverall, the main difference between a traditional neural network and a RNN is the way they process input data. A traditional neural network processes input independently, while a RNN processes input sequentially and maintains a memory of the data it has seen so far. This allows a RNN to make better use of the context and order of the input data to make more accurate predictions.\nDifferent Variants of RNN Vanilla RNN: This is the simplest type of RNN, which has a single layer of neurons and a fixed-sized memory. The output of the RNN at each time step is determined by the current input and the previous hidden state. Long Short-Term Memory (LSTM): This type of RNN is designed to handle long-term dependencies in the data by introducing \u0026quot;memory cells\u0026quot; that can store information for long periods of time. LSTMs also have gates that control the flow of information into and out of the memory cells, allowing them to retain or forget information as needed. Gated Recurrent Unit (GRU): This type of RNN is similar to an LSTM, but it has fewer parameters and is often easier to train. Like an LSTM, a GRU has gates that control the flow of information into and out of the hidden state, but it does not have separate memory cells. Bidirectional RNN: This type of RNN processes the input data in two directions, from the beginning to the end and from the end to the beginning. This allows the RNN to incorporate information from the future as well as the past, which can be useful for tasks such as language modeling. Each of these types of RNN has its own strengths and weaknesses, and they are suitable for different types of tasks. For example, a vanilla RNN might be suitable for processing short sequences of data, while an LSTM or GRU might be better for longer sequences. A bidirectional RNN might be useful for tasks where the order of the input data is important, such as language modeling.\nDifference Between LSTM and GRU The main difference between an LSTM and a GRU is the way they handle the flow of information through the hidden state. An LSTM has separate memory cells and gates that control the flow of information into and out of these cells, while a GRU has a single memory cell and uses gates to control the flow of information into and out of this cell.\nAn example of how you might define an LSTM using Keras:\n1model = tf.keras.Sequential() 2 3# Add an input layer, which expects input with shape (batch_size, input_length, input_dim). 4model.add(tf.keras.layers.InputLayer(input_shape=(batch_size, input_length, input_dim))) 5 6# Add an LSTM layer with 32 units. 7model.add(tf.keras.layers.LSTM(units=32)) 8 9# Add a dense layer with 10 units and a softmax activation function. 10model.add(tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;)) An example of how you might define a GRU using Keras:\n1model = tf.keras.Sequential() 2 3# Add an input layer, which expects input with shape (batch_size, input_length, input_dim). 4model.add(tf.keras.layers.InputLayer(input_shape=(batch_size, input_length, input_dim))) 5 6# Add a GRU layer with 32 units. 7model.add(tf.keras.layers.GRU(units=32)) 8 9# Add a dense layer with 10 units and a softmax activation function. 10model.add(tf.keras.layers.Dense(units=10, activation=\u0026#39;softmax\u0026#39;)) When to Use Bidirectional RNN A bidirectional RNN is a type of RNN that processes the input data in two directions: from the beginning to the end and from the end to the beginning. This allows the RNN to incorporate information from the future as well as the past, which can be useful for tasks such as language modeling.\nIn contrast, an LSTM processes the input data in a single direction and maintains a memory of the input data. This allows the LSTM to handle long-term dependencies in the data, but it does not have access to information from the future.\nWhen to use a bidirectional RNN over an LSTM depends on the specific characteristics of the input data and the task you are trying to solve. If the order of the input data is important and you need to incorporate information from the future as well as the past, then a bidirectional RNN might be a good choice. For example, if you are building a language model and you want to predict the next word in a sentence, a bidirectional RNN can take into account the words that come before and after the current word to make a more accurate prediction.\nOn the other hand, if the input data does not have a clear order or if you only need to use information from the past to make predictions, then an LSTM might be a better choice. For example, if you are building a model to forecast stock prices, the order of the data might not be as important as the long-term trends and patterns. In this case, an LSTM can use its memory to capture these trends and make more accurate predictions.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/understanding-recurrent-neural-networks/","section":"artificial-intelligence","tags":["deep learning","machine learning","NLP","algorithm"],"title":"How A Recurrent Neural Network Works"},{"body":"Bit Manipulation Bit manipulation is a technique used in computer science to manipulate data at the level of its binary representation. This can be useful for a variety of tasks, such as low-level optimization, data compression, and cryptography.\nAt its core, bit manipulation involves working with individual bits, rather than larger units of data such as bytes or words. This is typically done using bitwise operators, which perform operations on the individual bits of a number. Some common bitwise operators include AND, OR, and XOR, which perform logical operations on the bits of two numbers. For example, the AND operator compares the bits of two numbers and returns 1 if both bits are 1, otherwise it returns 0.\nOne common use of bit manipulation is in the optimization of computer programs. Since computers operate on binary data, performing operations on individual bits can be more efficient than working with larger units of data. For example, a program might use bit manipulation to quickly check if a number is even or odd, or to set or clear individual bits in a number.\nAnother use of bit manipulation is in data compression. By manipulating the bits of a data file, it is possible to reduce the amount of space that the file takes up on disk or in memory. This can be useful for storing large amounts of data in a compact format, or for transmitting data over a network more efficiently.\nFinally, bit manipulation is also used in cryptography. Cryptography is the practice of using mathematical algorithms to encrypt and decrypt data, in order to protect it from unauthorized access. Bit manipulation is often used to implement these algorithms, in order to make them more efficient and secure.\nSigned Integer vs Unsigned Integer In computer science, an integer is a data type that represents a whole number. This can include positive numbers, negative numbers, or 0.\nA signed integer is an integer that can represent both positive and negative numbers, as well as 0. This is typically represented using a number's most significant bit (the leftmost bit) to represent the sign of the number. A 0 in the most significant bit indicates a positive number, while a 1 in the most significant bit indicates a negative number. For example, the binary number 1000 would be -8 in a signed integer representation.\nAn unsigned integer, on the other hand, is an integer that can only represent non-negative numbers (including 0). This means that all bits in an unsigned integer are used to represent the magnitude of the number, without a separate sign bit. For example, the binary number 1000 would be 8 in an unsigned integer representation.\nIn general, signed integers allow for a wider range of values than unsigned integers, because they can represent negative as well as positive numbers. However, unsigned integers can store larger positive numbers within the same number of bits.\nBit Shifting vs Bit Masking Bitwise operators are operators that perform bit-level operations on operands. These operators are typically used to manipulate individual bits in an operand. Some common bitwise operators include AND, OR, XOR, and NOT.\nBit shifting is the act of shifting the bits in a binary number to the left or right. This can be useful for quickly multiplying or dividing a number by two.\nBit masking is the act of using a bit mask to extract or manipulate specific bits in a binary value. A bit mask is a binary value with some number of bits set to 1 and the rest set to 0. By performing a bitwise AND operation between a value and a bit mask, it is possible to extract specific bits from the value.\n","link":"https://blog.sksoumik.com/software-engineering/bit_manupulation_in_python/","section":"software-engineering","tags":["programming","python"],"title":"Unleashing the Power of Bit Manipulation in Computer Science"},{"body":"Cleaning text for natural language processing (NLP) tasks is an important step that can help improve the performance of your model. In this blog post, we will discuss some common text cleaning techniques and how to apply them to your text data.\nThe first step in cleaning text for NLP is to remove any noisy or irrelevant information. This can include things like HTML tags, URLs, and other extraneous characters. Removing this information can help the model focus on the relevant content and improve its performance.\nAnother common text cleaning step is to standardize the text. This can include things like converting the text to lowercase, removing punctuation, and expanding abbreviations. Standardizing the text can help the model better understand the content and improve its performance.\nAnother important text cleaning step is to remove stop words. Stop words are words that are commonly used in a language but do not convey any meaning, such as \u0026quot;the,\u0026quot; \u0026quot;and,\u0026quot; and \u0026quot;but.\u0026quot; Removing stop words can help the model focus on the important content and improve its performance.\nOnce you have cleaned your text data, it is important to check that it is in the correct format for your model. For example, many NLP models expect the input text to be tokenized, which means breaking the text into individual words or phrases. If your text is not already tokenized, you will need to tokenize it before feeding it to the model.\nHere are some examples of text cleaning methods with code samples:\nRemoving HTML tags To remove HTML tags from text data, you can use the BeautifulSoup library in Python.\n1from bs4 import BeautifulSoup 2 3html_text = \u0026#34;\u0026lt;p\u0026gt;This is some text with \u0026lt;strong\u0026gt;HTML\u0026lt;/strong\u0026gt; tags.\u0026lt;/p\u0026gt;\u0026#34; 4 5# Use BeautifulSoup to remove the HTML tags 6soup = BeautifulSoup(html_text, \u0026#39;html.parser\u0026#39;) 7cleaned_text = soup.get_text() 8 9print(cleaned_text) # Output: This is some text with HTML tags. Removing punctuation To remove punctuation from text data, you can use the string library in Python.\n1import string 2 3text = \u0026#34;This is some text with punctuation.!\u0026#34; 4 5# Use the string.punctuation property to get a string of all punctuation characters 6punctuation = string.punctuation 7 8# Use the translate() method to remove the punctuation from the text 9cleaned_text = text.translate(str.maketrans(\u0026#39;\u0026#39;, \u0026#39;\u0026#39;, punctuation)) 10 11print(cleaned_text) # Output: This is some text with punctuation Removing stop words To remove stop words from text data, you can use the nltk library in Python.\n1import nltk 2from nltk.corpus import stopwords 3 4text = \u0026#34;This is some text with stop words.\u0026#34; 5 6# Use the nltk.corpus.stopwords.words() method to get a list of stop words 7stop_words = stopwords.words(\u0026#39;english\u0026#39;) 8 9# Use a list comprehension to remove the stop words from the text 10cleaned_text = [word for word in text.split() if word not in stop_words] 11 12print(cleaned_text) # Output: [\u0026#39;This\u0026#39;, \u0026#39;text\u0026#39;, \u0026#39;stop\u0026#39;, \u0026#39;words.\u0026#39;] Removing numbers To remove numbers from text data, you can use a regular expression.\n1import re 2 3text = \u0026#34;This is some text with 1234 numbers.\u0026#34; 4 5# Use the re.sub() method to remove any sequences of digits from the text 6cleaned_text = re.sub(r\u0026#39;\\d+\u0026#39;, \u0026#39;\u0026#39;, text) 7 8print(cleaned_text) # Output: This is some text with numbers. Standardizing case To standardize the case of text data, you can use the str.lower() method in Python.\n1text = \u0026#34;This is some text with MIXED CASE.\u0026#34; 2 3# Use the str.lower() method to convert the text to lowercase 4cleaned_text = text.lower() 5 6print(cleaned_text) # Output: this is some text with mixed case. Stemming words To stem words in text data, you can use the nltk library in Python.\n1import nltk 2from nltk.stem import PorterStemmer 3 4text = \u0026#34;This is some text with stemming words.\u0026#34; 5 6# Use the PorterStemmer from the nltk.stem module to stem the words in the text 7stemmer = PorterStemmer() 8stemmed_words = [stemmer.stem(word) for word in text.split()] 9 10# Join the stemmed words into a single string 11cleaned_text = \u0026#39; \u0026#39;.join(stemmed_words) 12 13print(cleaned_text) # Output: this is some text with stem word. Removing whitespace To remove whitespace from text data, you can use the str.strip() method in Python.\n1text = \u0026#34; This is some text with whitespace. \u0026#34; 2 3# Use the str.strip() method to remove any leading or trailing whitespace 4cleaned_text = text.strip() 5 6print(cleaned_text) # Output: This is some text with whitespace. Removing accents To remove accents from text data, you can use the unidecode library in Python.\n1from unidecode import unidecode 2 3text = \u0026#34;This is some text with accented characters: é, í, ó, ú, ñ\u0026#34; 4 5# Use the unidecode.unidecode() method to remove the accents from the text 6cleaned_text = unidecode(text) 7 8print(cleaned_text) # Output: This is some text with accented characters: e, i, o, u, n Removing special characters To remove special characters from text data, you can use a regular expression.\n1import re 2 3text = \u0026#34;This is some text with special characters: !@#$%^\u0026amp;*()_+\u0026#34; 4 5# Use the re.sub() method to remove any non-alphanumeric characters from the text 6cleaned_text = re.sub(r\u0026#39;[^a-zA-Z0-9\\s]\u0026#39;, \u0026#39;\u0026#39;, text) 7 8print(cleaned_text) # Output: This is some text with special characters Removing newline characters To remove newline characters from text data, you can use the str.replace() method in Python.\n1text = \u0026#34;This is some text\\nwith newline characters\\n\u0026#34; 2 3# Use the str.replace() method to replace newline characters with a space 4cleaned_text = text.replace(\u0026#39;\\n\u0026#39;, \u0026#39; \u0026#39;) 5 6print(cleaned_text) # Output: This is some text with newline characters Removing duplicates To remove duplicate words from text data, you can use the set() function in Python. Here is an example:\n1text = \u0026#34;This is some text with duplicate words. Words words words.\u0026#34; 2 3# Use the set() function to remove duplicate words from the text 4cleaned_text = \u0026#39; \u0026#39;.join(set(text.split())) 5 6print(cleaned_text) # Output: This is some text with duplicate words. These are just a few more examples of text cleaning methods. There are many other techniques that you can use, depending on the specific requirements of your task.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/nlp-text-cleaning-methods-explained/","section":"artificial-intelligence","tags":["python","programming","NLP","data science"],"title":"Different Text Cleaning Methods for NLP Tasks"},{"body":"REST (Representational State Transfer) and gRPC are two different techniques for creating web APIs (Application Programming Interfaces).\nWhat is REST API? A popular architectural design for developing web APIs is REST. It is built on the HTTP (HyperText Transfer Protocol) and operates on resources defined by URLs using common HTTP methods including GET, POST, PUT, and DELETE. REST APIs are simple to use with a variety of programming languages and tools since they exchange data using the JSON (JavaScript Object Notation) format.\nREST is a robust approach to web APIs that enables the development of intricate systems with numerous associated resources. A REST API's URL structure frequently illustrates the connections between various resources, making it simple to understand the API and interact with its data.\nWhat is gRPC? A high-performance, open-source framework for creating web APIs is called gRPC. It is more efficient and compact than JSON since it exchanges data using the Protocol Buffers binary format. The fact that gRPC offers bi-directional streaming, which enables clients and servers to send and receive data in real-time while maintaining low latency, makes it the perfect choice for applications that manage massive amounts of data.\nA service is described as a group of methods that may be called over the network, and the foundation of gRPC is the concept of services. Protobuf files, which are used to create client and server stubs automatically, include descriptions of these methods. This produces a strongly-typed API where the message's structure and data types are predefined, improving type verification and error handling.\nDifferences between REST API and gRPC Data format: REST APIs use JSON, while gRPC uses Protocol Buffers. Protocol Buffers are smaller and faster than JSON, but are less human-readable and require a code generation step.\nAPI design: REST APIs are based on resource-oriented URLs and HTTP methods, while gRPC is based on defining services and methods in a Protobuf file. This leads to a more strongly-typed API in gRPC, but can make the API more difficult to evolve.\nPerformance: gRPC is generally faster and more efficient than REST because it uses binary data and a compact message format, and supports bi-directional streaming. REST is typically slower and less efficient because it uses text-based data and requires larger message sizes.\nLanguage support: gRPC has official client libraries for many programming languages, and is easier to use with languages that have native support for Protocol Buffers, such as C++, Java, and Go. REST is more language-agnostic, but requires more manual work to handle serialization and deserialization of data.\nError handling: gRPC has built-in error handling, with the ability to return detailed error information in response to a failed request. REST typically relies on HTTP status codes to indicate success or failure, and additional error information must be included in the response body.\nWhen to use REST API? When you require a flexible, language-independent API that is simple to use. When you want to exchange data via a standardized, known protocol. Sometimes, you must integrate JSON-using systems. When you wish to develop a simple, easy-to-use API without needing bi-directional streaming or real-time communication. When you want to make it possible for your API to easily modify over time without requiring significant changes to the underlying code. When to use gRPC? When you need high performance and efficiency, especially for programs that handle with lots of data or demand fast response times. Sometimes a strongly-typed API with integrated error handling is what you wish to use. When you are utilizing a language, such as C++, Java, or Go, that comes with native support for Protocol Buffers. When you need to provide real-time data transmission and reception between clients and servers via bi-directional streaming. To strengthen type checking and error handling and to impose a consistent structure and data types for your API. Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/choosing-right-web-api-rest-grpc/","section":"software-engineering","tags":["system design","software engineering"],"title":"Comparing REST API and gRPC - Choosing the Right Web API"},{"body":"Imagine you’ve written a letter in English, but your friend only speaks Spanish. You’d need someone (or something) to translate it for them, right? In programming, your code is like that letter, and the computer only understands a special language called \u0026quot;machine language\u0026quot; (all 0s and 1s). A compiler and an interpreter are like translators that help the computer understand what you’ve written in languages like Python, C++, or Java.\nCompiler: Takes your entire letter (code) and translates it into Spanish (machine language) all at once. Then, your friend (the computer) can read it whenever they want without needing the translator again.\nInterpreter: Translates your letter line by line, on the spot, while your friend reads it. No full translation is saved—just whatever’s needed right then.\nLet’s use a cooking analogy to make this even easier:\nCompiler: Think of it like a chef who gets a full recipe (your code), prepares the entire meal (translates it to machine language), and serves it ready-to-eat. Once the meal is made, you can eat it anytime without needing the chef again. This happens before the program runs.\nInterpreter: This is like a chef who reads the recipe line by line and cooks each step as you ask for it. If you say, “Make the soup,” they make the soup right then. No full meal is prepped ahead—you get it piece by piece while the program runs.\nHere’s a quick comparison:\nAspect Compiler Interpreter Translation Time Translates entire code at once before execution Translates and executes code line by line Execution Speed Generally faster as code is pre-translated Generally slower due to line-by-line translation Error Detection Shows all errors at once during compilation Stops at first error during execution Memory Usage Requires more memory to store compiled code Uses less memory as it translates on-the-fly Examples C, C++, Java, Rust, Go Python, JavaScript, Ruby ","link":"https://blog.sksoumik.com/software-engineering/compiler-vs-interpreter-in-simple-terms/","section":"software-engineering","tags":["programming","software-engineering","system-design"],"title":"Compiler vs Interpreter - A Simple Guide for Beginners"},{"body":"Dynamic programming is a method for solving complex problems by breaking them down into smaller subproblems. It is a mathematical optimization technique that is mainly used for solving problems that exhibit the properties of overlapping subproblems and optimal substructure.\nThe basic idea behind dynamic programming is to solve a complex problem by breaking it down into smaller subproblems, solving each of those subproblems just once, and storing their solutions. The solutions to the subproblems are then used to solve the original problem.\nHere is a step-by-step guide to understanding dynamic programming with examples:\n1. Identify the subproblems The first step in using dynamic programming to solve a problem is to identify the subproblems that make up the original problem. These subproblems should be small enough to be solved independently, but they should also be related to the original problem in some way.\nFor example, suppose we want to find the shortest path between two points on a map. In this case, the subproblems would be the individual segments of the path between the start and end points. Each subproblem would involve finding the shortest path between two adjacent points on the map.\n2. Develop a recursive solution Once the subproblems have been identified, the next step is to develop a recursive solution to the original problem. This means expressing the solution to the original problem in terms of solutions to the subproblems.\nFor example, suppose we want to find the shortest path between two points on a map, and we have already identified the subproblems as the individual segments of the path. We can then develop a recursive solution by expressing the shortest path between the start and end points in terms of the shortest paths between the intermediate points on the path.\n3. Store the solutions to the subproblems The next step is to store the solutions to the subproblems so that they can be used to solve the original problem. This is where the \u0026quot;programming\u0026quot; part of dynamic programming comes in. We need to create a data structure (such as an array or a hash table) to store the solutions to the subproblems.\nFor example, suppose we want to find the shortest path between two points on a map, and we have already identified the subproblems and developed a recursive solution. We can store the solutions to the subproblems in an array, with the array index representing the position on the map and the array value representing the shortest path from the start to that position.\n4. Use the stored solutions to solve the original problem The final step is to use the stored solutions to the subproblems to solve the original problem. This is done by using the recursive solution developed in step 2 and the stored solutions to the subproblems to compute the solution to the original problem.\nFor example, suppose we want to find the shortest path between two points on a map, and we have already identified the subproblems, developed a recursive solution, and stored the solutions to the subproblems. We can then use the stored solutions to compute the shortest path between the start and end points by applying the recursive solution to the stored solutions.\nDynamic programming is a powerful technique for solving complex problems, but it can be difficult to understand and apply. However, by following the steps outlined above, you can gain a better understanding of how dynamic programming works and how to use it to solve problems.\nDifferent Approaches for Dynamic Programming There are two main approaches to dynamic programming: the top-down approach and the bottom-up approach.\nTop-down approach The top-down approach to dynamic programming involves solving the original problem by breaking it down into smaller subproblems and solving each of those subproblems recursively. This approach is also known as memoization, because it involves storing the solutions to the subproblems in a memo (i.e. a data structure such as an array or a hash table) and using them to solve the original problem.\nThe top-down approach is typically implemented using recursion. For example, suppose we want to find the shortest path between two points on a map. We can use the top-down approach to solve this problem by developing a recursive function that takes the current position on the map as an argument and returns the shortest path from the start to that position. The function would then be called recursively to compute the shortest path between the intermediate points on the map.\nHere is an example of the top-down approach to dynamic programming in Python:\n1def shortest_path_top_down(map, start, end): 2 # Store the solutions to the subproblems in a memo 3 memo = {} 4 5 # Define a recursive function to compute the shortest path between two points on a map 6 def shortest_path_recursive(current): 7 # If the current position is the end position, return 0 8 if current == end: 9 return 0 10 11 # If the current position has already been visited, return the stored solution 12 if current in memo: 13 return memo[current] 14 15 # Set the minimum path length to infinity 16 min_path_length = float(\u0026#34;inf\u0026#34;) 17 18 # Iterate over the adjacent positions 19 for next_pos in map[current]: 20 # Compute the shortest path to the next position using the recursive solution 21 path_length = 1 + shortest_path_recursive(next_pos) 22 23 # Update the minimum path length 24 min_path_length = min(min_path_length, path_length) 25 26 # Store the minimum path length in the memo 27 memo[current] = min_path_length 28 29 # Return the minimum path length 30 return min_path_length 31 32 # Call the recursive function to compute the shortest path between the start and end points 33 return shortest_path_recursive(start) 34 35 36if __name__ == \u0026#34;__main__\u0026#34;: 37 # Define the map 38 map = { 39 \u0026#34;A\u0026#34;: [\u0026#34;B\u0026#34;, \u0026#34;C\u0026#34;], 40 \u0026#34;B\u0026#34;: [\u0026#34;D\u0026#34;, \u0026#34;E\u0026#34;], 41 \u0026#34;C\u0026#34;: [\u0026#34;F\u0026#34;], 42 \u0026#34;D\u0026#34;: [], 43 \u0026#34;E\u0026#34;: [\u0026#34;F\u0026#34;], 44 \u0026#34;F\u0026#34;: [] 45 } 46 47 # Define the start and end points 48 start = \u0026#34;A\u0026#34; 49 end = \u0026#34;F\u0026#34; 50 51 # Compute the shortest path between the start and end points 52 shortest_path_length = shortest_path_top_down(map, start, end) 53 54 # Print the shortest path length 55 print(shortest_path_length) # Bottom-up approach The bottom-up approach to dynamic programming involves solving the smaller subproblems first and then using their solutions to solve the original problem. This approach is also known as tabulation, because it involves storing the solutions to the subproblems in a table (i.e. an array or a matrix) and using them to compute the solution to the original problem.\nThe bottom-up approach is typically implemented using iteration. For example, suppose we want to find the shortest path between two points on a map. We can use the bottom-up approach to solve this problem by iterating over the positions on the map and computing the shortest path from the start to each position using the solutions to the subproblems (i.e. the shortest paths to the adjacent positions).\nHere is an example of the bottom-up approach to dynamic programming in Python:\n1# Function to compute the shortest path between two points on a map using the bottom-up approach 2def shortest_path_bottom_up(map, start, end): 3 # Store the solutions to the subproblems in an array 4 shortest_paths = [float(\u0026#34;inf\u0026#34;)] * (len(map) + 1) 5 6 # Set the shortest path from the start to the start to 0 7 shortest_paths[start] = 0 8 9 # Iterate over the positions on the map 10 for i in range(1, len(map) + 1): 11 # If the current position is not the end position 12 if i != end: 13 # Iterate over the adjacent positions 14 for next_pos in map[i]: 15 # Compute the shortest path to the next position using the recursive solution 16 path_length = 1 + shortest_paths[next_pos] 17 18 # Update the shortest path to the current position 19 shortest_paths[i] = min(shortest_paths[i], path_length) 20 21 # Return the shortest path from the start to the end 22 return shortest_paths[end] This function takes the map (as a list of lists of adjacent positions), the start position, and the end position as arguments, and returns the shortest path between the start and end positions. The function stores the solutions to the subproblems in an array and uses them to compute the shortest path between each position on the map.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/dynamic-programming-step-by-step-explanation/","section":"software-engineering","tags":["programming","algorithm"],"title":"Dynamic Programming - Step by Step Guide with Examples"},{"body":"Let's say you've got a big school project to do. You need to write a report, paint a picture, and build a model. Here's how you might tackle the project under different scenarios:\nMulti-processing This is like having multiple copies of yourself, each working independently on a different part of the project. One of you is writing the report, another one is painting the picture, and the third one is building the model. You can all work at the same time on different tasks. This makes things faster because instead of one person doing everything one after the other, you have multiple \u0026quot;yous\u0026quot; doing different tasks at the same time.\nMulti-threading Now, instead of having multiple copies of yourself, imagine you're a superhero who can move very quickly. You start writing the report, then zoom over to paint a bit of the picture, then zoom over to work on the model, then back to the report, and so forth. You're still one person, but you can switch tasks so quickly it almost seems like you're doing everything at once.\nTechnically,\nIn a multi-processing system, multiple processes run concurrently on different cores or CPUs. Each process has its own memory space and runs independently of the others. This is useful when tasks are CPU-intensive and can be run independently. The Operating System manages these processes, assigning them to different cores and handling the inter-process communication.\nIn a multi-threading system, multiple threads run concurrently within the same process. Each thread shares the same memory space and resources of the process. This is useful when tasks are I/O-intensive and can be run concurrently. The Operating System manages these threads, scheduling them to run on different cores and handling the inter-thread communication.\nWhen to use Multi-threading vs Multi-processing? Let's look into some examples where each technique might be best suited:\nMulti-Processing Data Analysis and Scientific Computing: Multi-processing is often used in data analysis, machine learning, or scientific computing where a large amount of data is processed. If the computations are independent and CPU-intensive, they can be distributed across multiple processes to utilize multiple cores and CPUs. This can lead to significant improvements in execution time. Threads might not be as beneficial here because they operate within the same process and share the same memory, which does not take full advantage of multiple cores or CPUs. Concurrency in Microservices Architecture: In microservices architecture, where different services are decoupled and perform different tasks, multi-processing allows each service to run in a separate process, isolated from others. This approach enhances the reliability of the system, as a failure in one service (process) does not directly affect the others. Also, each microservice can be developed, deployed, and scaled independently. Multi-Threading Web Servers: A web server needs to handle multiple incoming client requests at the same time. With multi-threading, each request can be handled by a separate thread. This is more efficient than multi-processing because threads share memory and can communicate with each other more easily than separate processes can. Creating a new thread for each request is also generally faster and uses less resources than creating a new process. Graphical User Interfaces (GUIs): In applications with a GUI, multi-threading is often used to keep the interface responsive. For example, one thread can handle user input, while another performs background tasks. Without this separation, the GUI could become unresponsive when performing a long-running task. Using multi-processing in this case would be overkill and would not provide any significant benefits. Understanding whether a task is I/O-bound or CPU-bound Understanding whether a task is I/O-bound or CPU-bound can help determine the best strategy to handle concurrency in your application. Here are some guidelines to identify each type of task:\nI/O-Bound Tasks: These are tasks where the program spends most of its time waiting for Input/Output (I/O) operations to complete. This could include things like reading from or writing to disk files, making network requests (like API calls or database queries), or getting user input.\nFor example, if your program is downloading files from the internet, the time it takes to complete is largely dependent on your network speed and the server's response time, not how fast your CPU can process data. The CPU is just waiting most of the time, making this an I/O-bound task.\nCPU-Bound Tasks: These are tasks where the program spends most of its time performing computations, and the speed at which they complete is largely dependent on the speed of the CPU.\nFor example, if your program is performing complex mathematical calculations, encoding/decoding video, or processing large amounts of data, the time it takes to complete these tasks is largely dependent on how fast your CPU can process these calculations. These tasks are keeping the CPU busy, making them CPU-bound tasks.\nTo optimize I/O-bound tasks, you might consider using multi-threading or asynchronous programming. This can allow your program to continue doing work while waiting for I/O operations to complete, rather than just waiting around.\nFor CPU-bound tasks, you might consider using multi-processing, as this can allow your program to take advantage of multiple CPUs or cores and perform computations in parallel.\nImplementing Multi-threading in Python In Python, threads can be implemented with the use of threading module or concurrent.futures . Now let’s consider a function that is used to download an image — this is clearly a I/O-bound task:\n1import requests 2 3 4def download_img(img_url: str): 5\t\u0026#34;\u0026#34;\u0026#34; 6\tDownload image from img_url in curent directory 7\t\u0026#34;\u0026#34;\u0026#34; 8\tres = requests.get(img_url, stream=True) 9\tfilename = f\u0026#34;{img_url.split(\u0026#39;/\u0026#39;)[-1]}.jpg\u0026#34; 10 11\twith open(filename, \u0026#39;wb\u0026#39;) as f: 12\tfor block in res.iter_content(1024): 13\tf.write(block) Now, let’s try to download a few images from Unsplash using the code snippet below.\n1import requests 2 3 4def download_img(img_url: str): 5\tres = requests.get(img_url, stream=True) 6\tfilename = f\u0026#34;{img_url.split(\u0026#39;/\u0026#39;)[-1]}.jpg\u0026#34; 7 8\twith open(filename, \u0026#39;wb\u0026#39;) as f: 9\tfor block in res.iter_content(1024): 10\tf.write(block) 11 12 13if __name__ == \u0026#39;__main__\u0026#39;: 14 # a list of different image URLs 15 images = [ 16 \u0026#39;https://images.unsplash.com/photo-1509718443690-d8e2fb3474b7\u0026#39;, 17 \u0026#39;https://images.unsplash.com/photo-1587620962725-abab7fe55159\u0026#39;, 18 \u0026#39;https://images.unsplash.com/photo-1493119508027-2b584f234d6c\u0026#39;, 19 \u0026#39;https://images.unsplash.com/photo-1482062364825-616fd23b8fc1\u0026#39;, 20 \u0026#39;https://images.unsplash.com/photo-1521185496955-15097b20c5fe\u0026#39;, 21 \u0026#39;https://images.unsplash.com/photo-1510915228340-29c85a43dcfe\u0026#39;, 22 \u0026#39;https://images.unsplash.com/photo-1509718443690-d8e2fb3474b7\u0026#39;, 23 \u0026#39;https://images.unsplash.com/photo-1587620962725-abab7fe55159\u0026#39;, 24 \u0026#39;https://images.unsplash.com/photo-1493119508027-2b584f234d6c\u0026#39;, 25 \u0026#39;https://images.unsplash.com/photo-1482062364825-616fd23b8fc1\u0026#39;, 26 \u0026#39;https://images.unsplash.com/photo-1521185496955-15097b20c5fe\u0026#39;, 27 \u0026#39;https://images.unsplash.com/photo-1510915228340-29c85a43dcfe\u0026#39;, 28 ] 29 30 for img in images: 31 download_img(img) The above code is downloading images one by one which can be quite slow, especially if there are a lot of images. We can use threading to download multiple images at once. In Python, the concurrent.futures module provides a high-level interface for asynchronously executing callables, which simplifies multi-threading.\n1import requests 2import concurrent.futures 3 4def download_img(img_url: str): 5\tres = requests.get(img_url, stream=True) 6\tfilename = f\u0026#34;{img_url.split(\u0026#39;/\u0026#39;)[-1]}.jpg\u0026#34; 7 8\twith open(filename, \u0026#39;wb\u0026#39;) as f: 9\tfor block in res.iter_content(1024): 10\tf.write(block) 11 12if __name__ == \u0026#39;__main__\u0026#39;: 13 # a list of different image URLs 14 images = [ 15 \u0026#39;https://images.unsplash.com/photo-1509718443690-d8e2fb3474b7\u0026#39;, 16 \u0026#39;https://images.unsplash.com/photo-1587620962725-abab7fe55159\u0026#39;, 17 \u0026#39;https://images.unsplash.com/photo-1493119508027-2b584f234d6c\u0026#39;, 18 \u0026#39;https://images.unsplash.com/photo-1482062364825-616fd23b8fc1\u0026#39;, 19 \u0026#39;https://images.unsplash.com/photo-1521185496955-15097b20c5fe\u0026#39;, 20 \u0026#39;https://images.unsplash.com/photo-1510915228340-29c85a43dcfe\u0026#39;, 21 \u0026#39;https://images.unsplash.com/photo-1509718443690-d8e2fb3474b7\u0026#39;, 22 \u0026#39;https://images.unsplash.com/photo-1587620962725-abab7fe55159\u0026#39;, 23 \u0026#39;https://images.unsplash.com/photo-1493119508027-2b584f234d6c\u0026#39;, 24 \u0026#39;https://images.unsplash.com/photo-1482062364825-616fd23b8fc1\u0026#39;, 25 \u0026#39;https://images.unsplash.com/photo-1521185496955-15097b20c5fe\u0026#39;, 26 \u0026#39;https://images.unsplash.com/photo-1510915228340-29c85a43dcfe\u0026#39;, 27 ] 28 29 with concurrent.futures.ThreadPoolExecutor() as executor: 30 executor.map(download_img, images) In the revised code, we use concurrent.futures.ThreadPoolExecutor to create a pool of threads, and then we use the map method, which assigns a callable to each thread in the pool. The threads then execute the callables concurrently, which in this case means downloading multiple images at once. This should make the code faster when downloading a large number of images.\nImplementing Multi-processing in Python Let's take the task of calculating the factorial of a large number. This is a CPU-bound task, as it involves lots of calculations. First, we will implement it in a normal (synchronous) way, and then we will optimize it using multi-processing.\n1import math 2 3def calculate_factorial(n): 4 return math.factorial(n) 5 6if __name__ == \u0026#34;__main__\u0026#34;: 7 numbers = [10, 20, 30, 40, 50] * 100000 # Some large numbers 8 9 for num in numbers: 10 print(f\u0026#34;The factorial of {num} is {calculate_factorial(num)}\u0026#34;) Now, let's optimize the code using multi-processing.\n1import concurrent.futures 2import math 3 4def calculate_factorial(n): 5 return n, math.factorial(n) 6 7if __name__ == \u0026#34;__main__\u0026#34;: 8 numbers = [10, 20, 30, 40, 50] * 100000 # Some large numbers 9 10 with concurrent.futures.ProcessPoolExecutor() as executor: 11 for number, factorial in executor.map(calculate_factorial, numbers): 12 print(f\u0026#34;The factorial of {number} is {factorial}\u0026#34;) Here, we used concurrent.futures.ProcessPoolExecutor() to create a pool of worker processes. The executor.map() function then applies the calculate_factorial function to every item in the numbers list, distributing the tasks among the worker processes. Each process will calculate the factorial of a number independently of the others, which allows for parallel execution and can provide a significant speedup for large lists and CPU-bound tasks.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/multi-threading_vs_multi-processing/","section":"software-engineering","tags":["python","programming"],"title":"Multi-Threading vs Multi-Processing"},{"body":"There are several different types of recommender systems, each with its own unique characteristics and applications. Some of the most commonly used types of recommender systems include:\nContent-based recommender systems: These systems recommend items to users based on the characteristics of the items themselves. For example, a content-based recommender system for a movie website might recommend movies to users based on the genre, director, or actor.\nCollaborative filtering recommender systems: These systems make recommendations to users based on the preferences of similar users. For example, if two users have similar ratings for a set of movies, a collaborative filtering system might recommend the same movies to both users.\nHybrid recommender systems: These systems combine the strengths of both content-based and collaborative filtering systems to make more accurate recommendations. For example, a hybrid recommender system might use content-based techniques to identify a set of potential recommendations, and then use collaborative filtering to refine the recommendations based on the preferences of similar users.\nContent-based recommender systems A content-based recommender system is a type of recommendation engine that uses the characteristics of an item to recommend similar items. This is in contrast to collaborative filtering systems, which use the past behavior of users to make recommendations.\nHere's how a content-based recommender system works:\nFirst, the system needs to be trained on a dataset of items and their characteristics. For example, if the system is recommending movies, the dataset would include information about each movie's genre, actors, director, and other relevant characteristics. When a user makes a request for recommendations, the system analyzes the characteristics of the items that the user has expressed interest in. For example, if a user has watched several romantic comedies, the system will look for other romantic comedies with similar characteristics. The system then uses the characteristics of the user's favorite items to generate a list of recommendations. These recommendations may include items that the user has not yet expressed interest in, but which have similar characteristics to the items that the user likes. Finally, the system presents the recommendations to the user, who can then choose which items to interact with. Content-based recommender systems have several advantages over other types of recommendation engines. For one, they can make recommendations even for users who have not yet interacted with many items, as long as the system has been trained on a diverse dataset. Additionally, because the recommendations are based on the characteristics of the items, rather than the behavior of other users, the recommendations are more personalized and can better reflect the user's individual interests.\nCollaborative filtering recommender systems A collaborative filtering recommender system is a type of recommendation engine that uses the past behavior of users to make recommendations. This is in contrast to content-based recommender systems, which use the characteristics of an item to make recommendations.\nHere's how a collaborative filtering recommender system works:\nFirst, the system needs to be trained on a dataset of user interactions with items. For example, if the system is recommending movies, the dataset would include information about which movies each user has watched and how they rated them. When a user makes a request for recommendations, the system looks at the other users who have interacted with the same items as the user. For example, if a user has watched several romantic comedies and rated them highly, the system will look for other users who have also watched and rated those romantic comedies highly. The system then uses the behavior of these similar users to generate a list of recommendations. These recommendations may include items that the user has not yet interacted with, but which have been highly rated by other users who have similar tastes. Finally, the system presents the recommendations to the user, who can then choose which items to interact with. Collaborative filtering recommender systems have several advantages over other types of recommendation engines. For one, they can make recommendations even for users who have not yet interacted with many items, as long as there are other users with similar tastes. Additionally, because the recommendations are based on the behavior of other users, rather than the characteristics of the items, the recommendations can be more diverse and can introduce users to new items that they may not have discovered on their own.\nHybrid recommender systems A hybrid recommender system is a type of recommendation engine that combines the strengths of content-based and collaborative filtering recommender systems. This allows the system to make more accurate and personalized recommendations than either type of system alone.\nHere's how a hybrid recommender system works:\nFirst, the system needs to be trained on a dataset of user interactions with items, as well as the characteristics of each item. For example, if the system is recommending movies, the dataset would include information about which movies each user has watched, how they rated them, and the genre, actors, director, and other relevant characteristics of each movie. When a user makes a request for recommendations, the system uses both the characteristics of the items that the user has expressed interest in and the behavior of other users to generate a list of recommendations. For example, if a user has watched several romantic comedies and rated them highly, the system will look for other romantic comedies with similar characteristics and also look for other users who have watched and rated those romantic comedies highly. The system then uses this information to generate a list of recommendations, which may include items that the user has not yet expressed interest in, but which have similar characteristics to the items that the user likes and have been highly rated by other users with similar tastes. Finally, the system presents the recommendations to the user, who can then choose which items to interact with. Hybrid recommender systems have several advantages over other types of recommendation engines. Because they use both the characteristics of the items and the behavior of other users, they can make more accurate and personalized recommendations than either content-based or collaborative filtering systems alone. Additionally, because they can incorporate multiple types of information, they can be more flexible and adaptable to different situations and user preferences.\nAuthor: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/types-of-recommender-systems-machine-learning/","section":"artificial-intelligence","tags":["machine learning","data science"],"title":"Different Types of Recommendation Systems"},{"body":"","link":"https://blog.sksoumik.com/tags/math/","section":"tags","tags":null,"title":"math"},{"body":"Logarithms are mathematical operations that are the inverse of exponentiation. In other words, if we have a base b and an exponent x, the logarithm of the resulting number y to the base b is x. This can be written as log_b(y) = x.\nFor example, the logarithm of 1000 to base 10 is 3, because 10^3 = 1000. Similarly, the logarithm of 100 to base 10 is 2, because 10^2 = 100. The base of the logarithm is a fixed value that determines the scale of the logarithm. The most common base for logarithms is 10, which is referred to as the \u0026quot;common logarithm\u0026quot;.\nThe logarithm function grows very slowly as the input increases, making it a useful tool for expressing very large or very small numbers in a more manageable form. Logarithms are often used in mathematics, engineering, and computer science to simplify calculations and to represent data on a logarithmic scale.\nOne specific type of logarithm that is commonly used in computer science and mathematics is the base 2 logarithm, also known as the \u0026quot;binary logarithm\u0026quot;. This logarithm is used to represent numbers in binary form, which consists of only two digits: 0 and 1. For example, the base 2 logarithm of 8 is 3, because 2^3 = 8. Similarly, the base 2 logarithm of 16 is 4, because 2^4 = 16.\nThe base 2 logarithm is often used in computer science because it corresponds to the way that computers store and manipulate numbers using binary digits (bits). For example, the base 2 logarithm of a number can be used to determine the minimum number of bits needed to represent the number in binary form. In addition to its use in computer science, the base 2 logarithm is also used in mathematics and engineering to represent and analyze data on a logarithmic scale.\nOne example of how logarithms are used in computer science is in the binary search algorithm. Binary search is an efficient search algorithm that works by repeatedly dividing a sorted list in half and comparing the search key to the middle element of the list. The logarithmic time complexity of binary search makes it an efficient algorithm for finding an element in a large list.\nThe time complexity of binary search is expressed in terms of the logarithm function, specifically as O(log n), where n is the number of elements in the list being searched. This is because the number of recursive calls made by the algorithm is logarithmic in the size of the list, and each recursive call takes constant time to compare the search key to the middle element of the list.\nFor example, consider a list with 8 elements. To find an element in this list using binary search, the list would be divided into two sublists of size 4, and then each sublist would be divided into two sublists of size 2. This process would be repeated until the search key is found or the list is empty, resulting in a total of 3 recursive calls (since 2^3 = 8). In general, the time complexity of binary search is O(log n), where n is the number of elements in the list being searched.\n","link":"https://blog.sksoumik.com/software-engineering/understanding-logarithm-function-computer-science/","section":"software-engineering","tags":["programming","algorithms","math"],"title":"Understanding Logarithm Function"},{"body":"","link":"https://blog.sksoumik.com/archives/","section":"","tags":null,"title":""},{"body":"Decorator is a design pattern to extend the functionality of a function without modifying the structure of the original function. Decorators are usually applied to functions using the @decorator syntax, immediately before the function definition.\nExample of how to use a decorator to extend the functionality of a function:\n1def my_decorator(func): 2 def wrapper(*args, **kwargs): 3 # Do something before the function is called 4 result = func(*args, **kwargs) 5 # Do something after the function is called 6 return result 7 return wrapper 8 9@my_decorator 10def add(x, y): 11 return x + y The add function is decorated with the my_decorator function. When the add function is called, the code in the wrapper function will be executed before and after the call to add. This allows the my_decorator function to extend the functionality of the add function without modifying the structure of the add function itself.\nExample of a decorator function that calculates the execution time of a function:\n1import time 2 3def execution_time(func): 4 def wrapper(*args, **kwargs): 5 start = time.time() 6 result = func(*args, **kwargs) 7 end = time.time() 8 print(f\u0026#39;{func.__name__} took {end - start} seconds to execute.\u0026#39;) 9 return result 10 return wrapper 11 12@execution_time 13def some_function(): 14 time.sleep(2) 15 16some_function() # prints \u0026#34;some_function took 2.00 seconds to execute.\u0026#34; Some common decorators that are used in Django: @login_required: This decorator is used to require a user to be logged in to access a view. If a user is not logged in, they will be redirected to the login page. @permission_required: This decorator is used to require a user to have a specific permission in order to access a view. If a user does not have the required permission, they will be redirected to the login page. @csrf_exempt: This decorator is used to exempt a view from Django's built-in Cross-Site Request Forgery (CSRF) protection. This can be useful if you want to allow requests to be made to a view without requiring a CSRF token. @cache_page: This decorator is used to cache the results of a view, so that subsequent requests for the same view can be served from the cache instead of being generated by the view. This can improve the performance of your application. Use cases of decorators There are several reasons why you might want to use decorators in your Python code:\nDecorators can be used to extend the functionality of a function without modifying the structure of the original function. This allows you to keep your code clean and organized, and avoid duplication of code. Decorators can be used to add additional functionality to a function without changing the code inside the function. This can be useful for tasks such as logging, validating arguments, or measuring execution time. Decorators can be used to change the behavior of a function at runtime. This can be useful for tasks such as caching the results of a function, or retrying a function if it fails. Decorators can be applied to multiple functions, allowing you to reuse the same code for multiple functions. This can save you time and make your code more maintainable. Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/decorators_in_python/","section":"software-engineering","tags":["python","programming"],"title":"Decorators in Python"},{"body":"Very deep neural networks with a massive number of parameters are very robust machine learning systems. But, in this type of huge network, overfitting is a common serious problem. Learning how to deal with overfitting is essential to mastering machine learning. The fundamental issue in machine learning is the tension between optimization and generalization. Optimization refers to adjusting a model to get the best performance possible on the training data (the learning in machine learning).\nIn contrast, generalization refers to how well the trained model performs on the data it has never seen before (test set). The goal of the game is to get a good generalization. But, you don’t control generalization; you can only adjust the model based on its training data.\nHow do you know whether a model is overfitting? The clear sign of overfitting is when the model accuracy is high in the training set, but the accuracy drops significantly with new data or in the test set. This means the model knows the training data very well but can not generalize. This case makes your model useless in production or AB tests in most domains.\nHow to prevent overfitting? Okay, now let's say you found that your model overfits. But what to do now to prevent your model from overfitting? Fortunately, there are many ways you can try to prevent your model from overfitting. Below I have described a few of the most widely used solutions for overfitting.\n1. Reduce the network size The simplest way to prevent overfitting is to reduce the model's size: the number of learnable parameters in the model (which is determined by the number of layers and units per layer).\n2. Cross-Validation In cross-validation, the initial training data is used as small train-test splits. Then, these splits are used to tune the model. The most popular form of cross-validation is K-fold cross-validation, and K represents the number of folds. Here is a short video from Udacity which explains K-fold cross-validation very well.\n3. Add weight regularization Given two explanations for something, the explanation most likely to be correct is the simplest one — the one that makes fewer assumptions. This idea also applies to the models learned by neural networks: given some training data and network architecture, multiple sets of weight values could explain the data. Simpler models are less likely to overfit than complex ones. A simple model in this context is a model where the distribution of parameter values has less entropy (or a model with fewer parameters). Thus a common way to mitigate overfitting is to put constraints on the complexity of a network by forcing its weights to take only small values, which makes the distribution of weight values more regular. This is called weight regularization, and it’s done by adding to the loss function of the network a cost associated with having large weights. This cost comes in two flavors:\nL1 regularization — The cost added is proportional to the absolute value of the weight coefficients.\nL2 regularization — The cost added is proportional to the square of the value of the weight coefficients. L2 regularization is also called weight decay in the context of neural networks.\n4. Remove irrelevant features Improve the data by removing irrelevant features. A dataset may contain many features that do not contribute much to the prediction, and removing those less important features can improve accuracy and reduce overfitting. You can use the scikit-learn feature selection module for this purpose.\n5. Add dropout layer Dropout, applied to a layer, consists of randomly dropping out(setting to zero) several output features of the layer during training. A given layer typically returns a vector [0.2, 0.5, 1.3, 0.8, 1.1] for a given input sample during training. After applying dropout, this vector will have a few zero entries distributed randomly: for example, [0, 0.5, 1.3, 0, 1.1].\n6. Data Augmentation The simplest way to reduce overfitting is to increase the training data size. Let's consider we are dealing with images. In this case, there are a few ways of improving the training data size — rotating the image, flipping, scaling, shifting, etc. This technique is known as data augmentation, which usually provides a giant leap in improving the model's accuracy.\nThis blog was originally published at Medium\nDeep Learning with Python — Book by François Chollet https://elitedatascience.com/overfitting-in-machine-learning https://www.quora.com/How-do-we-know-whether-a-model-is-overfitting https://www.analyticsvidhya.com/ ","link":"https://blog.sksoumik.com/artificial-intelligence/tips-to-avoid-overfitting-machine-learning/","section":"artificial-intelligence","tags":["deep learning","machine learning"],"title":"How to Prevent Overfitting in Machine Learning Models"},{"body":"Breadth-first search (BFS) and depth-first search (DFS) are two algorithms for traversing graphs. These algorithms are used to search for specific nodes or to find the shortest path between two nodes in a graph.\nBFS The breadth-first search (BFS) algorithm is a graph traversal algorithm that explores all of the neighbors of a starting node before moving on to any of the neighbor's neighbors. It is called a \u0026quot;breadth-first\u0026quot; algorithm because it explores the neighbors at each level of the graph before moving on to the next level.\nHere is a step-by-step example of how the BFS algorithm works, using a simple graph as an example:\nStart by placing the starting node on a queue. Take the first node off the queue and explore all of its neighbors. For each of these neighbors, add them to the queue if they have not been explored yet. Take the next node off the queue and explore all of its neighbors. For each of these neighbors, add them to the queue if they have not been explored yet. Repeat this process until the queue is empty, or until you have found the goal node (if you are searching for a specific node in the graph). Here is some sample Python code that implements the BFS algorithm:\n1# define the graph as a dictionary where the keys are the nodes and the values are the neighbors of each node 2graph = { 3 \u0026#39;A\u0026#39;: [\u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;E\u0026#39;], 4 \u0026#39;B\u0026#39;: [\u0026#39;A\u0026#39;, \u0026#39;D\u0026#39;, \u0026#39;E\u0026#39;], 5 \u0026#39;C\u0026#39;: [\u0026#39;A\u0026#39;, \u0026#39;F\u0026#39;, \u0026#39;G\u0026#39;], 6 \u0026#39;D\u0026#39;: [\u0026#39;B\u0026#39;], 7 \u0026#39;E\u0026#39;: [\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;D\u0026#39;], 8 \u0026#39;F\u0026#39;: [\u0026#39;C\u0026#39;], 9 \u0026#39;G\u0026#39;: [\u0026#39;C\u0026#39;] 10} 11 12# define the BFS function, which takes in the graph and the starting node as inputs 13def BFS(graph, start): 14 # create a queue to store the nodes that need to be explored 15 queue = [] 16 # add the starting node to the queue 17 queue.append(start) 18 # create a set to store the nodes that have been explored 19 visited = set() 20 # while the queue is not empty, continue exploring nodes 21 while queue: 22 # take the first node off the queue 23 node = queue.pop(0) 24 # if the node has not been explored yet, explore it 25 if node not in visited: 26 # add the node to the set of explored nodes 27 visited.add(node) 28 # add the neighbors of the node to the queue 29 neighbors = graph[node] 30 for neighbor in neighbors: 31 queue.append(neighbor) 32 # return the set of explored nodes 33 return visited 34 35# test the BFS function by finding all of the nodes that can be reached from the starting node \u0026#39;A\u0026#39; 36print(BFS(graph, \u0026#39;A\u0026#39;)) 37# this should return {\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;E\u0026#39;, \u0026#39;D\u0026#39;, \u0026#39;F\u0026#39;, \u0026#39;G\u0026#39;} Complexity Analysis:\nThe time complexity of BFS is O(|V| + |E|), where |V| is the number of nodes in the graph and |E| is the number of edges. This is because BFS explores all of the nodes and edges in the graph. The space complexity of BFS is O(|V|), where |V| is the number of nodes in the graph. This is because BFS uses a queue to store the nodes that need to be explored, and the size of the queue can grow up to |V| in the worst case. DFS The depth-first search (DFS) algorithm is a graph traversal algorithm that explores as far as possible along each branch before moving to the next branch. It is called a \u0026quot;depth-first\u0026quot; algorithm because it explores the depth of the graph first before exploring the breadth.\nHere is a step-by-step example of how the DFS algorithm works, using a simple graph as an example:\nStart by placing the starting node on a stack. Take the top node off the stack and explore all of its neighbors. For each of these neighbors, add them to the stack if they have not been explored yet. Take the next node off the stack and explore all of its neighbors. For each of these neighbors, add them to the stack if they have not been explored yet. Repeat this process until the stack is empty, or until you have found the goal node (if you are searching for a specific node in the graph). Here is some sample Python code that implements the DFS algorithm:\n1# define the graph as a dictionary where the keys are the nodes and the values are the neighbors of each node 2graph = { 3 \u0026#39;A\u0026#39;: [\u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;E\u0026#39;], 4 \u0026#39;B\u0026#39;: [\u0026#39;A\u0026#39;, \u0026#39;D\u0026#39;, \u0026#39;E\u0026#39;], 5 \u0026#39;C\u0026#39;: [\u0026#39;A\u0026#39;, \u0026#39;F\u0026#39;, \u0026#39;G\u0026#39;], 6 \u0026#39;D\u0026#39;: [\u0026#39;B\u0026#39;], 7 \u0026#39;E\u0026#39;: [\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;D\u0026#39;], 8 \u0026#39;F\u0026#39;: [\u0026#39;C\u0026#39;], 9 \u0026#39;G\u0026#39;: [\u0026#39;C\u0026#39;] 10} 11 12# define the DFS function, which takes in the graph and the starting node as inputs 13def DFS(graph, start): 14 # create a stack to store the nodes that need to be explored 15 stack = [] 16 # add the starting node to the stack 17 stack.append(start) 18 # create a set to store the nodes that have been explored 19 visited = set() 20 # while the stack is not empty, continue exploring nodes 21 while stack: 22 # take the top node off the stack 23 node = stack.pop() 24 # if the node has not been explored yet, explore it 25 if node not in visited: 26 # add the node to the set of explored nodes 27 visited.add(node) 28 # add the neighbors of the node to the stack 29 neighbors = graph[node] 30 for neighbor in neighbors: 31 stack.append(neighbor) 32 # return the set of explored nodes 33 return visited 34 35# test the DFS function by finding all of the nodes that can be reached from the starting node \u0026#39;A\u0026#39; 36print(DFS(graph, \u0026#39;A\u0026#39;)) 37# this should return {\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;D\u0026#39;, \u0026#39;E\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;F\u0026#39;, \u0026#39;G\u0026#39;} Complexity Analysis:\nThe time complexity of DFS is O(|V| + |E|), where |V| is the number of nodes in the graph and |E| is the number of edges. This is because DFS explores all of the nodes and edges in the graph. The space complexity of DFS is O(|V|), where |V| is the number of nodes in the graph. This is because DFS uses a stack to store the nodes that need to be explored, and the size of the stack can grow up to |V| in the worst case. When to Use BFS and When to Use DFS? The choice between using the breadth-first search (BFS) or the depth-first search (DFS) algorithm depends on the specific problem you are trying to solve and the characteristics of the data you are working with.\nHere are some general guidelines for when to use each algorithm:\nUse BFS when you want to find the shortest path between two nodes in a graph. This is because BFS always explores the nodes at each level of the graph before moving on to the next level, so it is guaranteed to find the shortest path if one exists. Use DFS when the graph is very large and you want to save memory. This is because DFS only explores a few nodes as deep as possible before moving on to the next branch, so it uses less memory than BFS. Use DFS when you want to find all of the nodes in a connected component of a graph. This is because DFS explores each branch of the graph as deeply as possible, so it is guaranteed to find all of the nodes in a connected component if one exists. Use BFS when the graph is not very large and you want to find the shortest path between two nodes, even if the graph is not connected. This is because BFS explores all of the neighbors of a starting node before moving on to any of the neighbor's neighbors, so it is guaranteed to find the shortest path between two nodes if one exists. Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/understanding-graph-traversal-bfs-dfs/","section":"software-engineering","tags":["algorithms","problem solving"],"title":"Understanding Graph Traversal - BFS vs DFS"},{"body":"","link":"https://blog.sksoumik.com/about.pt/","section":"","tags":null,"title":"Sobre"},{"body":"Check Prime Number A prime number is a positive integer greater than 1 that has no positive integer divisors other than 1 and itself. For example, the first six prime numbers are 2, 3, 5, 7, 11, and 13.\nThe number 6 is not a prime number because it has more than two positive integer divisors. In fact, the divisors of 6 are 1, 2, 3, and 6.\nTo determine if a number is prime, you can check if it has any positive integer divisors other than 1 and itself. If it does, then it is not a prime number.\nProblem: Write a function that determines if a given integer is a prime number.\n1def is_prime(n): 2 if n \u0026lt;= 1: 3 return False 4 5 for i in range(2, n): 6 if n % i == 0: 7 return False 8 return True You can then use this above function to find all the prime numbers within a given range by using a loop:\n1def find_primes(n): 2 primes = [] 3 for i in range(2, n+1): 4 if is_prime(i): 5 primes.append(i) 6 return primes 7 8print(find_primes(10)) # Output: [2, 3, 5, 7] The time complexity of the is_prime() function is O(n), since the time it takes to execute the function is directly proportional to the value of the input n. This is because the function uses a for loop that iterates over all the values from 2 to n (not including n) and checks if n is divisible by each value.\nThe space complexity of the is_prime() function is O(1), since the function only uses a constant amount of memory regardless of the size of the input. This is because the function only uses a few variables (n and i) and does not create any new data structures.\nOptimization\nOne optimization is to check if n is even before entering the loop. If n is even, we can immediately return False because even numbers are not prime (except for 2). This optimization reduces the time complexity from O(n) to O(1) for even input numbers.\n1def is_prime(n): 2 if n \u0026lt;= 1: 3 return False 4 5 if n % 2 == 0: 6 return n == 2 7 8 for i in range(3, n, 2): 9 if n % i == 0: 10 return False 11 12 return True Check Palindrome Number A palindrome number is a number that remains the same when its digits are reversed. For example, 121, 11, and 55555 are palindrome numbers because they are the same when their digits are reversed. On the other hand, 123, 456, and 789 are not palindrome numbers because they are not the same when their digits are reversed.\nMethod 1 To check if a number is a palindrome in Python, you can convert the number to a string and compare the string with its reverse.\n1def is_palindrome(n: int): 2 n = str(n) 3 return n == n[::-1] 4 5print(is_palindrome(121)) # Output: True 6print(is_palindrome(123)) # Output: False Method 2 You can check if a number is a palindrome by comparing the digits of the number from left to right and right to left using two pointers.\n1def is_palindrome(n: int): 2 n = str(n) 3 left = 0 4 right = len(n) - 1 5 6 while left \u0026lt; right: 7 if n[left] != n[right]: 8 return False 9 10 # increment the left pointer 11 left += 1 12 # decrease the right pointer 13 right -= 1 14 15 return True 16 17print(is_palindrome(121)) # Output: True 18print(is_palindrome(123)) # Output: False Method 3 Without converting the number to string.\n1#without converting to string 2def is_palindrome2(x: int): 3 # any negative number is not a palindrome 4 if x \u0026lt; 0: 5 return False 6 7 reverse = 0 8 original = x 9 while x \u0026gt; 0: 10 # Adding the last digit of x to the reverse variable. 11 reverse = reverse * 10 + x % 10 12 # The same as `x = x // 10` 13 x = x // 10 14 15 return reverse == original The time complexity of the is_palindrome2() function is O(n), where n is the number of digits in the number x. This is because the function uses a while loop that iterates over the digits of x and performs a constant amount of work on each iteration.\nThe space complexity of the is_palindrome2() function is O(1), since the function only uses a constant amount of memory regardless of the size of the input. This is because the function only uses a few variables (x, reverse, and original) and does not create any new data structures.\nMerge Sort Merge sort is a divide and conquer sorting algorithm that works by recursively dividing a list into smaller sublists, sorting each sublist, and then merging the sublists back together.\n1def merge_sort(arr): 2 if len(arr) \u0026lt;= 1: 3 return arr 4 5 # Divide the list into two halves 6 mid = len(arr) // 2 7 left = arr[:mid] 8 right = arr[mid:] 9 10 # Recursively sort the two halves 11 left = merge_sort(left) 12 right = merge_sort(right) 13 14 # Merge the sorted halves 15 return merge(left, right) 16 17def merge(left, right): 18 result = [] 19 while left and right: 20 if left[0] \u0026lt; right[0]: 21 result.append(left.pop(0)) 22 else: 23 result.append(right.pop(0)) 24 result.extend(left) 25 result.extend(right) 26 return result 27 28# Test the merge_sort function 29arr = [5, 3, 2, 1, 4] 30print(merge_sort(arr)) # Output: [1, 2, 3, 4, 5] The time complexity of merge sort is O(n log n), where n is the number of elements in the list being sorted. This is because the algorithm works by recursively dividing the list into smaller sublists and then merging the sublists back together, which takes O(n log n) time in the worst case.\nThe space complexity of merge sort is O(n), since the algorithm uses additional space to store the sublists and the merged list.\nIn the context of merge sort, the logarithmic term \u0026quot;log n\u0026quot; refers to the number of times the list is divided in half during the sorting process.\nIn each step of the merge sort algorithm, the list is divided into two equal-sized sublists. The process is repeated recursively on each sublist until the sublists are of size 1 (i.e., they are sorted). The number of times the list is divided in half is equal to the number of recursive calls needed to sort the list, which is logarithmic in the size of the list.\nFor example, consider a list with 8 elements. To sort this list using merge sort, the list would be divided into two sublists of size 4, and then each sublist would be divided into two sublists of size 2. This process would be repeated until each sublist has only one element, resulting in a total of 3 recursive calls (since 2^3 = 8). In this case, log function will have a base 2, as the we are splitting each list into 2 in every recursive calls.\nBinary Search Binary search is an efficient search algorithm that works by repeatedly dividing a sorted list in half and comparing the search key to the middle element of the list. If the search key is equal to the middle element, the search is successful and the index of the element is returned. If the search key is less than the middle element, the search continues in the left half of the list. If the search key is greater than the middle element, the search continues in the right half of the list.\nBinary search only works if they given list is already sorted.\n1def fn_binary_search(arr, target): 2 left = 0 3 right = len(arr) - 1 4 while left \u0026lt;= right: 5 mid = (left + right) // 2 6 if target == arr[mid]: 7 # means we found the target, and return the index of the target 8 return mid 9 # if the target is in the right half of the array 10 elif target \u0026gt; arr[mid]: 11 left = mid + 1 12 # if the target is in the left half of the array 13 else: 14 right = mid - 1 15 # if the target is not in the array 16 return -1 Time complexity: O(log n)\nGenerate Fibonacci Series To generate the Fibonacci series from 1 to n, you can use a loop to iterate over the range of numbers from 1 to n and use a recursive function to compute each element in the series.\n1# find the n-th fibonacci number 2def fibonacci(n): 3 if n == 0: 4 return 0 5 elif n == 1: 6 return 1 7 else: 8 return fibonacci(n-1) + fibonacci(n-2) 9 10 11# generate the series that contains all numbers upto n 12def generate_fibonacci_series(n): 13 series = [] 14 for i in range(1, n+1): 15 series.append(fibonacci(i)) 16 return series 17 18# Test the generate_fibonacci_series function 19n = 10 20print(generate_fibonacci_series(n)) # Output: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] The time complexity of the above code is O(n^2), since the fibonacci() function takes O(2^n) time to compute each element in the series. The space complexity is O(n), since the generate_fibonacci_series() function uses a list to store the series and the fibonacci() function uses additional space to store the recursive calls.\nNote that this implementation of the Fibonacci series has a relatively high time complexity due to the use of a recursive function to compute each element in the series. A more efficient implementation of the Fibonacci series would use an iterative approach and have a lower time complexity.\n1def generate_fibonacci_series(n): 2 series = [0, 1] 3 for i in range(2, n+1): 4 series.append(series[i-1] + series[i-2]) 5 return series 6 7# Test the generate_fibonacci_series function 8n = 10 9print(generate_fibonacci_series(n)) # Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] In this implementation, the generate_fibonacci_series() function uses a for loop to iterate over the range of numbers from 2 to n and computes each element in the series using the previous two elements. This approach has a time complexity of O(n) and a space complexity of O(n), making it a more efficient way to generate the Fibonacci series.\nHow to reverse a string using Python without built-in functions One way to reverse a string in Python without using any built-in functions is to use a for loop to iterate over the characters in the string in reverse order and append them to a new string.\n1def reverse_string(string): 2 reversed_string = \u0026#34;\u0026#34; 3 for i in range(len(string) - 1, -1, -1): 4 reversed_string += string[i] 5 return reversed_string 6 7# Test the function 8print(reverse_string(\u0026#34;hello\u0026#34;)) # Output: \u0026#34;olleh\u0026#34; 9print(reverse_string(\u0026#34;world\u0026#34;)) # Output: \u0026#34;dlrow\u0026#34; Remove duplicate numbers from a list Using built-in function\n1def remove_duplicates(numbers): 2 return list(set(numbers)) 3 4# Test the function 5print(remove_duplicates([1, 2, 3, 3, 4, 4, 5, 5])) # Output: [1, 2, 3, 4, 5] Without built-in functions\n1def remove_duplicates(numbers): 2 unique_numbers = [] 3 for number in numbers: 4 if number not in unique_numbers: 5 unique_numbers.append(number) 6 return unique_numbers 7 8# Test the function 9print(remove_duplicates([1, 2, 3, 3, 4, 4, 5, 5])) # Output: [1, 2, 3, 4, 5] The time complexity of the remove_duplicates function is O(n), where n is the length of the input list. This is because the function requires one pass through the list to remove the duplicates.\nThe space complexity of the function is also O(n), because it creates a new list called unique_numbers to store the unique elements from the input list. The size of this list grows as the function adds more unique elements to it, so it requires O(n) space in the worst case.\nRemove duplicate characters from a string Using built-in function\n1def remove_duplicates(string): 2 return \u0026#34;\u0026#34;.join(set(string)) 3 4# Test the function 5print(remove_duplicates(\u0026#34;hello\u0026#34;)) # Output: \u0026#34;helo\u0026#34; 6print(remove_duplicates(\u0026#34;world\u0026#34;)) # Output: \u0026#34;wrdl\u0026#34; Without using built-in function\n1def remove_duplicates(string): 2 unique_characters = \u0026#34;\u0026#34; 3 for character in string: 4 if character not in unique_characters: 5 unique_characters += character 6 return unique_characters 7 8# Test the function 9print(remove_duplicates(\u0026#34;hello\u0026#34;)) # Output: \u0026#34;helo\u0026#34; 10print(remove_duplicates(\u0026#34;world\u0026#34;)) # Output: \u0026#34;wrdl\u0026#34; Count the occurrence of a given character in a string using built-in function\n1def count_occurrences(string, char): 2 return string.count(char) 3 4# Test the function 5print(count_occurrences(\u0026#34;hello\u0026#34;, \u0026#34;l\u0026#34;)) # Output: 2 6print(count_occurrences(\u0026#34;world\u0026#34;, \u0026#34;w\u0026#34;)) # Output: 1 Without using built-in function\n1def count_occurrences(string, char): 2 count = 0 3 for c in string: 4 if c == char: 5 count += 1 6 return count 7 8# Test the function 9print(count_occurrences(\u0026#34;hello\u0026#34;, \u0026#34;l\u0026#34;)) # Output: 2 10print(count_occurrences(\u0026#34;world\u0026#34;, \u0026#34;w\u0026#34;)) # Output: 1 Check if two strings are anagrams An anagram is a word or phrase formed by rearranging the letters of another word or phrase. For example, \u0026quot;listen\u0026quot; and \u0026quot;silent\u0026quot; are anagrams because they both contain the same letters, just arranged differently.\nHere are a few more examples of anagrams:\n\u0026quot;friend\u0026quot; and \u0026quot;finder\u0026quot; \u0026quot;elbow\u0026quot; and \u0026quot;below\u0026quot; \u0026quot;school\u0026quot; and \u0026quot;coolsh\u0026quot; Using built-in sorted function\n1def is_anagram(string1, string2): 2 return sorted(string1) == sorted(string2) 3 4# Test the function 5print(is_anagram(\u0026#34;hello\u0026#34;, \u0026#34;olleh\u0026#34;)) # Output: True 6print(is_anagram(\u0026#34;world\u0026#34;, \u0026#34;worlld\u0026#34;)) # Output: False Without built-in function\n1def is_anagram(string1, string2): 2 char_count1 = {} 3 char_count2 = {} 4 for c in string1: 5 if c in char_count1: 6 char_count1[c] += 1 7 else: 8 char_count1[c] = 1 9 for c in string2: 10 if c in char_count2: 11 char_count2[c] += 1 12 else: 13 char_count2[c] = 1 14 return char_count1 == char_count2 15 16# Test the function 17print(is_anagram(\u0026#34;hello\u0026#34;, \u0026#34;olleh\u0026#34;)) # Output: True 18print(is_anagram(\u0026#34;world\u0026#34;, \u0026#34;worlld\u0026#34;)) # Output: False Calculate the number of vowels and consonants in a string 1def count_vowels_consonants(string): 2 vowels = 0 3 consonants = 0 4 for c in string: 5 if c.lower() in \u0026#34;aeiou\u0026#34;: 6 vowels += 1 7 elif c.isalpha(): 8 consonants += 1 9 return vowels, consonants 10 11# Test the function 12print(count_vowels_consonants(\u0026#34;hello\u0026#34;)) # Output: (2, 3) 13print(count_vowels_consonants(\u0026#34;world\u0026#34;)) # Output: (1, 4) Find the second highest number in an integer array Using built-in function\n1def find_second_highest(arr): 2 max_number = float(\u0026#34;-inf\u0026#34;) 3 second_highest = float(\u0026#34;-inf\u0026#34;) 4 5 i = j = 0 6 7 while i \u0026lt; len(arr): 8 if arr[i] \u0026gt; max_number: 9 max_number = arr[i] 10 i = i +1 11 12 while j \u0026lt; len(arr): 13 if arr[j] \u0026gt; second_highest and arr[j] != max_number: 14 second_highest = arr[j] 15 j = j + 1 16 17 return second_highest 18 19 20 21# test the function 22array = [1, 2, 3, 4, 5] 23print(find_second_highest(array)) # should print 4 The time complexity of the find_second_highest function is O(n), because the function performs two sequential scans of the input array, arr. In both scans, the function examines each element in the array once, so the time complexity is linear with respect to the size of the array.\nThe space complexity of the function is also O(1), because the function only uses a constant amount of additional space regardless of the size of the input array. The function uses two variables (max_number and second_highest) to store the maximum and second highest values, but these variables do not change in size based on the size of the input array.\nFind the largest and smallest number from an array of integers 1# list of integers 2numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] 3 4# initialize variables 5largest = smallest = numbers[0] 6 7# iterate over the rest of the list 8for num in numbers[1:]: 9 if num \u0026gt; largest: 10 largest = num 11 if num \u0026lt; smallest: 12 smallest = num 13 14# print the results 15print(\u0026#34;The largest number is:\u0026#34;, largest) 16print(\u0026#34;The smallest number is:\u0026#34;, smallest) Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/basic_coding_problems_that_you_must_know_as_a_cse_graduate/","section":"software-engineering","tags":["algorithms","leetcode","problem solving"],"title":"Basic Coding Problems You Must Be Able to Solve As a Computer Science Graduate"},{"body":"","link":"https://blog.sksoumik.com/tags/django/","section":"tags","tags":null,"title":"django"},{"body":"","link":"https://blog.sksoumik.com/series/django/","section":"series","tags":null,"title":"django"},{"body":"What's the architecture of Django framework? The architecture of Django is based on the Model-View-Controller (MVC) pattern, which is a way of organizing code in a web application. In Django, this pattern is modified slightly and is referred to as Model-View-Template (MVT).\nHere's a simple explanation of each component in Django's architecture:\nModel: This component represents the data and database schema of your web application. Models define the structure of your data and provide an API for querying and manipulating that data.\nView: This component handles the business logic of your web application. Views retrieve data from models, process it, and then render a response that is returned to the client.\nTemplate: This component defines the layout and structure of the HTML that is sent to the client. Templates are typically populated with data from views.\nIn Django, the framework provides a lot of the boilerplate code for handling common web development tasks, such as handling requests, interacting with databases, and managing user authentication. Developers can focus on writing the specific code that defines the behavior and features of their web application.\nHow the MVT is different from MVC? Controller vs. Template: In the MVC pattern, the controller is responsible for managing the flow of data between the model and view. In MVT, this responsibility is split between the view and template components. Views handle the business logic and retrieve data from the model, while templates define the presentation of the data.\nWhat makes Django a better framework than other Python Frameworks? Compared to other web frameworks, Django provides a lot of features out-of-the-box, such as the admin interface, ORM, form handling, authentication, and caching. These features help developers to build complex web applications quickly and efficiently. Additionally, Django has a strong and active community, which provides good documentation, tutorials, and support.\nOne of the unique features of Django is its emphasis on \u0026quot;batteries included\u0026quot; philosophy. This means that Django comes with a lot of built-in functionality and tools that help to streamline development and make it easier to get started with a new project. For example, the Django admin interface allows developers to manage the site's content and data easily, without having to write custom code.\nAnother feature of Django is its ORM, which provides a high-level abstraction for database access. The ORM allows developers to define models in Python code and interact with the database without having to write SQL directly. This makes it easier to write maintainable and readable code, and it also helps to prevent SQL injection attacks.\nWhat's middleware in Django? Imagine you are a customer at a restaurant. You place an order with the waiter, who takes it to the kitchen. The kitchen prepares the food and sends it back to the waiter, who then serves it to you.\nNow, let's say there is a middleman between you and the waiter. This middleman is responsible for checking your order and making sure it's correct, as well as ensuring that the kitchen receives the order and prepares the food correctly. Once the food is ready, the middleman checks it again to make sure it's correct before sending it back to the waiter to serve to you.\nIn this scenario, the middleman is acting as a middleware. They are intercepting the order (request) and the food (response) between you and the restaurant (server) to perform some additional tasks and checks.\nSimilarly, in web development, middleware is a component that sits between the client (user) and the server (application). It intercepts the incoming request from the client and performs some additional tasks, such as checking for authentication, caching, or error handling, before passing the request on to the server. It also intercepts the server's response and performs some additional tasks before sending the response back to the client.\nSo, middleware acts as a middleman between the client and the server, adding additional functionality and processing to the request and response handling process. It allows developers to add custom functionality to their application without modifying the core functionality of the server or client.\nWhat's the difference between a Django project and a Django app? In Django, a project is a collection of configurations and settings for a specific website or web application. It typically contains multiple apps and represents the top-level structure of your Django application.\nOn the other hand, an app is a module that encapsulates a specific functionality or set of related functionalities within a Django project. It is a self-contained unit that can be reused in other projects as well. Each app in Django can have its own models, views, templates, and static files.\nTo put it simply, a Django project is a collection of multiple apps that work together to provide the functionality of the entire web application, whereas a Django app is a smaller component that provides a specific set of functionalities or features within the project.\nFor example, if you are building a blog website, you may have a project named \u0026quot;myblog\u0026quot; that contains multiple apps such as \u0026quot;blog\u0026quot;, \u0026quot;comments\u0026quot;, \u0026quot;users\u0026quot;, and \u0026quot;categories\u0026quot;. The \u0026quot;blog\u0026quot; app may contain models, views, and templates related to blog posts, while the \u0026quot;comments\u0026quot; app may contain functionality related to comments on blog posts.\nWhat's ORM in Django or in general? In web development, applications often need to interact with databases to store and retrieve data. However, databases work in a different way than most programming languages. This is where Object-Relational Mapping (ORM) comes in.\nAn ORM is a technique that maps objects in a programming language to tables in a database. In other words, it allows developers to interact with a database using objects and methods, instead of writing SQL queries directly.\nIn Django, the ORM is a built-in component that provides a way to interact with databases using Python classes and objects. Developers define classes in Python that represent database tables and use the ORM to interact with the data stored in those tables.\nFor example, let's say you have a database table that stores information about customers. Using the Django ORM, you would define a Python class called \u0026quot;Customer\u0026quot; that maps to that table. You could then use methods provided by the ORM to create, read, update, and delete records in that table.\nThe advantage of using an ORM like Django's is that it makes it easier for developers to work with databases. They can focus on writing Python code and let the ORM handle the details of interacting with the database. This also makes it easier to switch to a different database backend without having to change a lot of code.\nFor example, if you want to create a database model using Django ORM:\n1from django.db import models 2 3class Customer(models.Model): 4 name = models.CharField(max_length=50) 5 email = models.EmailField() 6 created_at = models.DateTimeField(auto_now_add=True) If you want to create a database model using SQLAlchemy ORM:\n1from sqlalchemy import Column, Integer, String, DateTime 2from sqlalchemy.ext.declarative import declarative_base 3 4Base = declarative_base() 5 6class Customer(Base): 7 __tablename__ = \u0026#39;customers\u0026#39; 8 9 id = Column(Integer, primary_key=True) 10 name = Column(String(50)) 11 email = Column(String(50)) 12 created_at = Column(DateTime, server_default=\u0026#39;now()\u0026#39;) How do Django views work, and what is their purpose? A view is a Python function that takes a web request and returns a web response. Simply put, views are the code that generates the content that you see on a web page.\nHere's a simple example to help illustrate how views work:\nLet's say you have a Django app that displays a list of products on a web page. When a user visits that page, their web browser sends a request to the Django server asking for the page. Django receives that request and uses a view function to generate the content of the page. The view function will typically do the following:\nRetrieve data from a database or other data source. Process that data in some way (e.g., sort it, filter it, etc.). Render a template that includes the processed data. Return a response containing the rendered template. Once the response is generated, Django sends it back to the user's web browser, which then displays the web page.\nViews can also handle other types of requests, such as POST requests (used to submit data to a server), or AJAX requests (used to dynamically update parts of a web page without reloading the entire page).\nHow do you implement authentication and authorization in Django? Django provides a built-in authentication system that allows developers to authenticate users based on a username and password combination. This authentication system can be used out of the box or can be customized to meet specific requirements. To implement authentication in Django, the first step is to enable the authentication middleware by adding the following line to the MIDDLEWARE setting in the project's settings.py file:\n1MIDDLEWARE = [ 2 # ... 3 \u0026#39;django.contrib.auth.middleware.AuthenticationMiddleware\u0026#39;, 4 # ... 5] Next, we need to define the login and logout views in the project's urls.py file. Django provides a built-in view called LoginView, which handles the login process. We can use this view in our urls.py file as follows:\n1from django.contrib.auth.views import LoginView, LogoutView 2 3urlpatterns = [ 4 # ... 5 path(\u0026#39;login/\u0026#39;, LoginView.as_view(), name=\u0026#39;login\u0026#39;), 6 path(\u0026#39;logout/\u0026#39;, LogoutView.as_view(), name=\u0026#39;logout\u0026#39;), 7 # ... 8] Once the login and logout views are defined, we can protect our views by adding the @login_required decorator to them. This decorator ensures that only authenticated users can access the protected views. For example:\n1from django.contrib.auth.decorators import login_required 2from django.shortcuts import render 3 4@login_required 5def my_view(request): 6 # View logic goes here 7 return render(request, \u0026#39;my_template.html\u0026#39;, {}) To implement authorization in Django, we can use the built-in permission system. Permissions are associated with models and can be assigned to users or groups. Django provides several built-in permissions, such as view, add, change, and delete. To use permissions, we need to define them in the models.py file and associate them with the appropriate user or group. For example:\n1from django.db import models 2from django.contrib.auth.models import User 3 4class MyModel(models.Model): 5 name = models.CharField(max_length=100) 6 7 class Meta: 8 permissions = [ 9 (\u0026#34;can_view_mymodel\u0026#34;, \u0026#34;Can view MyModel\u0026#34;), 10 (\u0026#34;can_edit_mymodel\u0026#34;, \u0026#34;Can edit MyModel\u0026#34;), 11 ] 12 13 def __str__(self): 14 return self.name In the above example, we have defined two permissions: can_view_mymodel and can_edit_mymodel. These permissions can be assigned to users or groups using the Django admin interface or programmatically using Django's built-in permission API.\nHow do you perform database migrations in Django? To perform database migrations in Django, we use the Django's built-in migration framework. The migration framework provides a set of tools to manage database schema changes, including creating and applying migrations, generating SQL statements for schema changes, and managing version control of migrations.\nTo create a new migration, we use the makemigrations command, which inspects the current database schema and generates a set of migration files that describe the changes to be made. The migration files are created in the migrations directory of the app that the models belong to. For example, to create a new migration for an app named myapp, we would run the following command:\n1python manage.py makemigrations myapp This command creates a new migration file in the myapp/migrations directory, which we can then customize to add or modify fields, create new tables, or perform other database schema changes.\nTo apply the migrations, we use the migrate command, which applies all pending migrations to the database. For example, to apply the migrations for the myapp app, we would run the following command:\n1python manage.py migrate myapp This command applies all the pending migrations for the myapp app, which may include creating new tables, adding or removing fields, or modifying existing tables.\nIn addition to the makemigrations and migrate commands, Django also provides other commands and tools to manage database migrations, such as showmigrations, sqlmigrate, and migrations.\nORM and Query Optimization for performance improvement To optimize queries, We can use Django's built-in query optimization tools such as select_related, prefetch_related, and annotate. We can also use Django's raw SQL functionality when necessary to write optimized queries that leverage the full power of the underlying database.\nTo deal with complex database relationships, we can use Django's model fields such as ForeignKey, OneToOneField, and ManyToManyField, as well as custom model methods and querysets. In cases where the default behavior of the ORM did not suit your needs, you can create custom migrations to modify the database schema to support the necessary relationships.\nWe can also use Django's caching framework to improve performance for frequently accessed data. By caching data in memory, we can avoid making repeated database queries, which can significantly reduce page load times.\nselect_related is a tool that allows Django to retrieve related objects for a queryset in a single SQL query, rather than making separate queries for each related object. This can significantly reduce the number of queries needed to retrieve data and can improve performance. select_related is best used when working with models that have ForeignKey or OneToOneField relationships.\nprefetch_related is similar to select_related, but it retrieves many-to-many relationships in addition to foreign key and one-to-one relationships. It does this by retrieving all related objects in a single query and then organizing them in memory. prefetch_related is best used when working with models that have many-to-many or reverse foreign key relationships.\nannotate is a method that allows you to add calculated fields to a queryset based on related data. For example, you could use annotate to add a calculated field to a queryset that represents the number of related objects for each item in the queryset. annotate is best used when working with calculated fields or aggregate functions that require related data.\nIn general, select_related should be used when working with models that have ForeignKey or OneToOneField relationships, prefetch_related should be used when working with models that have many-to-many or reverse foreign key relationships, and annotate should be used when working with calculated fields or aggregate functions that require related data.\nExample of Using select_related, prefetch_related, and annotate:\n1# Using select_related to reduce the number of queries 2# Here, we\u0026#39;re retrieving all posts and their authors 3# Instead of making a separate query for each author, we use select_related to retrieve all authors in a single query 4posts = Post.objects.select_related(\u0026#39;author\u0026#39;) 5 6# Using prefetch_related to retrieve many-to-many relationships 7# Here, we\u0026#39;re retrieving all posts and their associated tags 8# Instead of making a separate query for each post-tag relationship, we use prefetch_related to retrieve all tags in a single query 9posts = Post.objects.prefetch_related(\u0026#39;tags\u0026#39;) 10 11# Using annotate to add a calculated field to a queryset 12# Here, we\u0026#39;re adding a field to a queryset that represents the number of comments on each post 13posts = Post.objects.annotate(num_comments=Count(\u0026#39;comments\u0026#39;)) 14 15# Using both select_related and prefetch_related in the same query 16# Here, we\u0026#39;re retrieving all posts and their authors and tags 17# We use select_related to retrieve the author of each post in a single query 18# We use prefetch_related to retrieve all tags for each post in a single query 19posts = Post.objects.select_related(\u0026#39;author\u0026#39;).prefetch_related(\u0026#39;tags\u0026#39;) Example of using raw SQL in Django:\n1from django.db import connection 2 3def get_users_with_most_posts(): 4 # write a raw SQL query that retrieves the users with the most posts 5 raw_query = \u0026#39;\u0026#39;\u0026#39; 6 SELECT auth_user.id, auth_user.username, COUNT(blog_post.id) AS num_posts 7 FROM auth_user 8 INNER JOIN blog_post ON blog_post.author_id = auth_user.id 9 GROUP BY auth_user.id 10 ORDER BY num_posts DESC 11 LIMIT 10 12 \u0026#39;\u0026#39;\u0026#39; 13 14 # execute the raw SQL query using Django\u0026#39;s database connection 15 with connection.cursor() as cursor: 16 cursor.execute(raw_query) 17 # Retrieve the results of the query 18 rows = cursor.fetchall() 19 20 # convert the rows into a list of dictionaries for easier use in Django templates 21 results = [] 22 for row in rows: 23 results.append({ 24 \u0026#39;user_id\u0026#39;: row[0], 25 \u0026#39;username\u0026#39;: row[1], 26 \u0026#39;num_posts\u0026#39;: row[2], 27 }) 28 29 return results Different types of relationships that can be defined between models In Django, there are four types of relationships that can be defined between models: ForeignKey, OneToOneField, ManyToManyField, and OneToOneRel.\nForeignKey: A ForeignKey is a many-to-one relationship between two models. It is used to define a relationship where a single object of one model belongs to another model. OneToOneField: A OneToOneField is a one-to-one relationship between two models. It is used to define a relationship where each object of one model corresponds to exactly one object of another model. ManyToManyField: A ManyToManyField is a many-to-many relationship between two models. It is used to define a relationship where each object of one model can have multiple objects of another model, and vice versa. OneToOneRel: A OneToOneRel is a reverse one-to-one relationship between two models. It is used to define a relationship where each object of one model is related to exactly one object of another model, but the relationship is defined on the second model. 1from django.db import models 2 3class Author(models.Model): 4 name = models.CharField(max_length=100) 5 6class Book(models.Model): 7 title = models.CharField(max_length=200) 8 author = models.ForeignKey(Author, on_delete=models.CASCADE) 9 10class UserProfile(models.Model): 11 user = models.OneToOneField(User, on_delete=models.CASCADE) 12 bio = models.TextField() 13 14class Tag(models.Model): 15 name = models.CharField(max_length=100) 16 books = models.ManyToManyField(Book) 17 18class Publisher(models.Model): 19 name = models.CharField(max_length=100) 20 books = models.OneToOneField(Book, on_delete=models.CASCADE) In this example, we have defined the following relationships:\nBook has a many-to-one relationship with Author using a ForeignKey. UserProfile has a one-to-one relationship with User using a OneToOneField. Tag has a many-to-many relationship with Book using a ManyToManyField. Publisher has a one-to-one relationship with Book using a OneToOneField. Difference between OneToOneField and OneToOneRel Let's say we have two models: Person and Passport. A person can have only one passport, and a passport can be owned by only one person. So, we can define a one-to-one relationship between the Person and Passport models using a OneToOneField on the Passport model:\n1from django.db import models 2 3class Person(models.Model): 4 name = models.CharField(max_length=100) 5 6class Passport(models.Model): 7 number = models.CharField(max_length=50) 8 person = models.OneToOneField(Person, on_delete=models.CASCADE) In this example, the Passport model has a OneToOneField to the Person model. This means that each Passport object is associated with exactly one Person object, and each Person object is associated with at most one Passport object.\nNow, when we access a Person object, we can use the reverse relationship to access its associated Passport object. This reverse relationship is represented by the OneToOneRel attribute on the Person model:\n1person = Person.objects.get(pk=1) 2passport = person.passport Here, person.passport returns the Passport object associated with the person instance. This is possible because Passport has a OneToOneField to Person, which creates a reverse relationship (OneToOneRel) from Person to Passport.\nCan you explain how Django handles HTTP requests? Let's start by understanding that HTTP stands for Hypertext Transfer Protocol. It's a set of rules that allows our browsers (like Chrome or Firefox) to talk to servers (where websites live) and ask for webpages. This asking is called an HTTP request.\nWhen a user makes an HTTP request, like clicking on a link or typing in a URL, the request is sent to the server where the Django application lives.\nImagine an HTTP request as a letter. This letter is sent to Django's house, which is the server. Django, like a careful reader, opens the letter and reads what's inside.\nThe content of the letter (the HTTP request) includes information like:\nThe URL: This is like the specific question you want to ask Django. For example, \u0026quot;Can I see the homepage?\u0026quot; or \u0026quot;Can I see the page about dogs?\u0026quot; Each question corresponds to a different URL. The method: This is like the action you want Django to take. For example, \u0026quot;GET\u0026quot; is like saying \u0026quot;Can I have...\u0026quot; and \u0026quot;POST\u0026quot; is like saying \u0026quot;Please take this...\u0026quot;. Now, Django uses something called a URL dispatcher to figure out where to send this request. It's like a postmaster who reads the address on a letter and decides which mailbox to put it in.\nThe URL dispatcher looks at the URL and matches it to a specific function in Django called a view. You can think of views like helpers or assistants in Django's house. Each view is responsible for a specific task, like showing you the homepage or the page about dogs.\nOnce the view gets the request, it does the work that's needed. It might pull information from a database (like a big filing cabinet of information), or it might prepare a form for the user to fill out.\nFinally, the view sends a response back to the user's browser. This response is like a return letter from Django. It's usually an HTML webpage, but it could also be an error message, a redirect to another page, or something else.\nAnd that's basically how Django handles an HTTP request!\n","link":"https://blog.sksoumik.com/software-engineering/understanding-django-framework-python/","section":"software-engineering","tags":["django","web-development","software-engineering"],"title":"Learning the Django Framework Fundamentals"},{"body":"","link":"https://blog.sksoumik.com/tags/web-development/","section":"tags","tags":null,"title":"web-development"},{"body":"Transfer learning is a technique in machine learning that allows a model trained on one task to be reused and fine-tuned for another similar task. The idea behind transfer learning is that a model that has already learned to recognize patterns in one set of data can be applied to a different but related problem, allowing the model to learn faster and with less data than if it were trained from scratch.\nThere are two main ways to perform transfer learning:\nFeature extraction: In this approach, you take a pre-trained model and remove the last layers (the ones that are responsible for making the final prediction), and add new layers on top. The pre-trained model has already learned useful features from the data, so by reusing these features, you can train a new classifier with less data. This is useful when you have a small dataset and want to leverage the knowledge of a pre-trained model. Fine-tuning: In this approach, you take a pre-trained model and unfreeze some of the layers near the bottom of the network, and then retrain the entire model with a new dataset. This allows the model to learn from both the new data and the pre-trained weights, which can lead to better performance on the new task. This is useful when you have a larger dataset and want to adjust the pre-trained model to work better for your specific task. Feature Extraction using Keras Here's an example of how you can use Keras to perform feature extraction using the ResNet50 model:\n1from keras.applications import ResNet50 2from keras.layers import Dense 3from keras.models import Model 4 5# Load the ResNet50 model with pre-trained weights 6base_model = ResNet50(weights=\u0026#39;imagenet\u0026#39;, include_top=False) 7 8# Freeze the layers of the model 9for layer in base_model.layers: 10 layer.trainable = False 11 12# Add a new fully connected layer for the output 13x = base_model.output 14x = Dense(1024, activation=\u0026#39;relu\u0026#39;)(x) 15predictions = Dense(10, activation=\u0026#39;softmax\u0026#39;)(x) 16 17# Create a new model that takes the base_model as input and the new output layer as output 18model = Model(inputs=base_model.input, outputs=predictions) 19 20# Compile and train the model 21model.compile(optimizer=\u0026#39;adam\u0026#39;, loss=\u0026#39;categorical_crossentropy\u0026#39;, metrics=[\u0026#39;accuracy\u0026#39;]) 22model.fit(X_train, y_train, epochs=10, batch_size=32) In this example, the ResNet50 model is loaded with pre-trained weights and all of its layers are frozen. Then, a new fully connected layer is added on top of the ResNet50 model, which is connected to the output of the model. This new fully connected layer is then trained using the X_train data and y_train labels.\nFine-tuning using Keras Here's an example of how you can use Keras to fine-tune the ResNet50 model:\n1from keras.applications import ResNet50 2from keras.layers import Dense 3from keras.models import Model 4 5# Load the ResNet50 model with pre-trained weights 6base_model = ResNet50(weights=\u0026#39;imagenet\u0026#39;, include_top=False) 7 8# Unfreeze some of the layers of the model 9for layer in base_model.layers[:15]: 10 layer.trainable = False 11for layer in base_model.layers[15:]: 12 layer.trainable = True 13 14# Add a new fully connected layer for the output 15x = base_model.output 16x = Dense(1024, activation=\u0026#39;relu\u0026#39;)(x) 17predictions = Dense(10, activation=\u0026#39;softmax\u0026#39;)(x) 18 19# Create a new model that takes the base_model as input and the new output layer as output 20model = Model(inputs=base_model.input, outputs=predictions) 21 22# Compile and train the model 23model.compile(optimizer=\u0026#39;adam\u0026#39;, loss=\u0026#39;categorical_crossentropy\u0026#39;, metrics=[\u0026#39;accuracy\u0026#39;]) 24model.fit(X_train, y_train, epochs=10, batch_size=32) In the above code,\nThe base_model variable is assigned the ResNet50 model with pre-trained weights on 'imagenet' dataset. By setting include_top=False it removes the last layers of the ResNet50 model, these layers are responsible for making the final prediction, so that we can add new layers on top. The next block uses a for loop to iterate over the layers of the base model. The first 15 layers are set to be untrainable, while the remaining layers are set to be trainable. This is done using the trainable attribute of the layers, which controls whether the gradients of the weights of the layer should be updated during training. Next, a new fully connected layer is added on top of the output of the base model. This layer, called x, applies a ReLU activation function to the input and has 1024 units. Then, another dense layer is added on top of the x layer, this is called predictions .This new layer applies a softmax activation function and has 10 units as this is a classification problem with 10 classes. Then, a new model is created with base_model as input and the new output layer as output. This is done by instantiating the Model class, passing in the base_model input and the new output layer. Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/artificial-intelligence/guide-to-transfer-learning-techniques/","section":"artificial-intelligence","tags":["deep learning","machine learning","data science"],"title":"Effective Transfer Learning - A Guide to Feature Extraction and Fine-Tuning Techniques"},{"body":"Dependency injection is a design pattern that is widely used in software development to promote good software design and make code more flexible and easier to test. In this blog post, we will take a closer look at what dependency injection is, how it works, and its advantages.\nWhat is Dependency Injection? Dependency injection is a technique that allows an object to receive its dependencies (other objects it needs to function) from the outside, rather than creating them itself. This makes the code more flexible and easier to test, since the dependencies can be easily swapped out for different implementations.\nReal World Example of Dependency Injection Imagine you are building a shopping cart application for an e-commerce website. Your application has a class called \u0026quot;CartController\u0026quot; that handles all the logic for managing the shopping cart. This class needs to use another class called \u0026quot;PaymentService\u0026quot; to process payments.\nIf the \u0026quot;CartController\u0026quot; class creates the \u0026quot;PaymentService\u0026quot; class itself, it becomes hard to test or change the payment gateway. But if the \u0026quot;CartController\u0026quot; class receives the \u0026quot;PaymentService\u0026quot; class as a dependency, it is much easier to test and change. You can easily swap out the \u0026quot;PaymentService\u0026quot; class with a different implementation that uses a different payment gateway, such as PayPal or Stripe.\nAnother advantage is that when you want to add some feature in future, you can just add the feature in the PaymentService class and you don't need to touch the CartController class.\nDependency injection makes the code more flexible, easier to test, and easier to change in the future.\nAn example of how dependency injection could be implemented in Python using the \u0026quot;CartController\u0026quot; and \u0026quot;PaymentService\u0026quot; classes from the previous example:\n1class PaymentService: 2 def __init__(self, gateway): 3 self.gateway = gateway 4 5 def process_payment(self, amount): 6 # code to process payment using the chosen gateway 7 pass 8 9class CartController: 10 def __init__(self, payment_service): 11 self.payment_service = payment_service 12 13 def checkout(self, cart_items, amount): 14 # code to calculate total amount 15 self.payment_service.process_payment(amount) In the above example, the \u0026quot;PaymentService\u0026quot; class takes the gateway as an input in the constructor, and the \u0026quot;CartController\u0026quot; class takes the payment_service as an input in the constructor. Here, the \u0026quot;CartController\u0026quot; class is dependent on \u0026quot;PaymentService\u0026quot; class, and it's not creating the object of PaymentService class inside it, rather it is getting it from outside.\nTo use these classes, we can create an instance of PaymentService class and pass it to the CartController class:\n1gateway = \u0026#34;Paypal\u0026#34; 2payment_service = PaymentService(gateway) 3cart = CartController(payment_service) This allows for easy testing and flexibility, as we can easily swap out the \u0026quot;PaymentService\u0026quot; class with a different implementation that uses a different payment gateway, such as Stripe or Authorize.net without changing the CartController class.\nAnother advantage is that when you want to add some feature in future, you can just add the feature in the PaymentService class and you don't need to touch the CartController class.\nAdvantages of Dependency Injection Dependency injection has several advantages that make it an important technique for software development.\nFlexibility: Dependency injection makes code more flexible, since dependencies can be easily swapped out for different implementations. Testability: Dependency injection makes code easier to test, since dependencies can be easily mocked or stubbed out. Reusability: Dependency injection makes code more reusable, since dependencies can be shared across multiple classes. Loose Coupling: Dependency injection promotes loose coupling between classes, which makes code more maintainable and easier to understand. Easy to Add new Feature: Dependency injection makes it easy to add new features to the codebase. You can just add the feature in the service class and you don't need to touch the controller class. Author: Sadman Kabir Soumik\n","link":"https://blog.sksoumik.com/software-engineering/dependency-injection-real-world-examples-advantages/","section":"software-engineering","tags":["programming","algorithm","python","software engineering"],"title":"Understanding Dependency Injection - Real-World Examples and Advantages"},{"body":"","link":"https://blog.sksoumik.com/tags/index/","section":"tags","tags":null,"title":"index"}]