Spaces:
Runtime error
Runtime error
Upload 9 files
Browse files- .gitignore +60 -0
- DEPLOYMENT.md +129 -0
- Dockerfile +13 -0
- README.md +65 -10
- app.py +130 -0
- index.html +175 -0
- requirements.txt +6 -0
- sentiment.pkl +3 -0
- test_api.py +71 -0
.gitignore
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
*$py.class
|
| 4 |
+
*.so
|
| 5 |
+
.Python
|
| 6 |
+
build/
|
| 7 |
+
develop-eggs/
|
| 8 |
+
dist/
|
| 9 |
+
downloads/
|
| 10 |
+
eggs/
|
| 11 |
+
.eggs/
|
| 12 |
+
lib/
|
| 13 |
+
lib64/
|
| 14 |
+
parts/
|
| 15 |
+
sdist/
|
| 16 |
+
var/
|
| 17 |
+
wheels/
|
| 18 |
+
share/python-wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
MANIFEST
|
| 23 |
+
|
| 24 |
+
# PyInstaller
|
| 25 |
+
*.manifest
|
| 26 |
+
*.spec
|
| 27 |
+
|
| 28 |
+
# Unit test / coverage reports
|
| 29 |
+
htmlcov/
|
| 30 |
+
.tox/
|
| 31 |
+
.nox/
|
| 32 |
+
.coverage
|
| 33 |
+
.coverage.*
|
| 34 |
+
.cache
|
| 35 |
+
nosetests.xml
|
| 36 |
+
coverage.xml
|
| 37 |
+
*.cover
|
| 38 |
+
*.py,cover
|
| 39 |
+
.hypothesis/
|
| 40 |
+
.pytest_cache/
|
| 41 |
+
cover/
|
| 42 |
+
|
| 43 |
+
# Environments
|
| 44 |
+
.env
|
| 45 |
+
.venv
|
| 46 |
+
env/
|
| 47 |
+
venv/
|
| 48 |
+
ENV/
|
| 49 |
+
env.bak/
|
| 50 |
+
venv.bak/
|
| 51 |
+
|
| 52 |
+
# IDEs
|
| 53 |
+
.vscode/
|
| 54 |
+
.idea/
|
| 55 |
+
*.swp
|
| 56 |
+
*.swo
|
| 57 |
+
|
| 58 |
+
# OS
|
| 59 |
+
.DS_Store
|
| 60 |
+
Thumbs.db
|
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Space Deployment Guide
|
| 2 |
+
|
| 3 |
+
## 🚀 How to Deploy Your Sentiment Analysis API to Hugging Face Spaces
|
| 4 |
+
|
| 5 |
+
### Step 1: Create a Hugging Face Account
|
| 6 |
+
1. Go to [https://huggingface.co](https://huggingface.co)
|
| 7 |
+
2. Sign up for a free account if you don't have one
|
| 8 |
+
|
| 9 |
+
### Step 2: Create a New Space
|
| 10 |
+
1. Go to [https://huggingface.co/new-space](https://huggingface.co/new-space)
|
| 11 |
+
2. Fill in the details:
|
| 12 |
+
- **Space name**: `sentiment-analysis-api` (or your preferred name)
|
| 13 |
+
- **License**: MIT
|
| 14 |
+
- **SDK**: Docker
|
| 15 |
+
- **Hardware**: CPU Basic (free tier)
|
| 16 |
+
|
| 17 |
+
### Step 3: Upload Your Files
|
| 18 |
+
Upload all the files from this directory to your new Space:
|
| 19 |
+
- `app.py` - FastAPI application
|
| 20 |
+
- `sentiment.pkl` - Your trained model
|
| 21 |
+
- `requirements.txt` - Python dependencies
|
| 22 |
+
- `Dockerfile` - Docker configuration
|
| 23 |
+
- `README.md` - Documentation
|
| 24 |
+
- `index.html` - Simple web interface (optional)
|
| 25 |
+
|
| 26 |
+
### Step 4: Your Space Will Auto-Deploy
|
| 27 |
+
- Hugging Face will automatically build and deploy your Docker container
|
| 28 |
+
- The build process takes 5-10 minutes
|
| 29 |
+
- You can monitor the build logs in the Space dashboard
|
| 30 |
+
|
| 31 |
+
### Step 5: Access Your API
|
| 32 |
+
Once deployed, your API will be available at:
|
| 33 |
+
- **API Base URL**: `https://YOUR_USERNAME-sentiment-analysis-api.hf.space`
|
| 34 |
+
- **Interactive Docs**: `https://YOUR_USERNAME-sentiment-analysis-api.hf.space/docs`
|
| 35 |
+
- **Simple Interface**: `https://YOUR_USERNAME-sentiment-analysis-api.hf.space` (if you uploaded index.html)
|
| 36 |
+
|
| 37 |
+
## 🧪 Testing Locally First
|
| 38 |
+
|
| 39 |
+
Before deploying, test your API locally:
|
| 40 |
+
|
| 41 |
+
1. **Install dependencies**:
|
| 42 |
+
```bash
|
| 43 |
+
pip install -r requirements.txt
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
2. **Run the API**:
|
| 47 |
+
```bash
|
| 48 |
+
python app.py
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
3. **Test the endpoints**:
|
| 52 |
+
- Visit `http://localhost:7860/docs` for interactive API docs
|
| 53 |
+
- Run `python test_api.py` to test all endpoints
|
| 54 |
+
- Visit `http://localhost:7860` for the simple web interface
|
| 55 |
+
|
| 56 |
+
## 📋 API Endpoints
|
| 57 |
+
|
| 58 |
+
### POST `/predict`
|
| 59 |
+
```json
|
| 60 |
+
{
|
| 61 |
+
"text": "I love this product!"
|
| 62 |
+
}
|
| 63 |
+
```
|
| 64 |
+
Response:
|
| 65 |
+
```json
|
| 66 |
+
{
|
| 67 |
+
"prediction": 1,
|
| 68 |
+
"confidence": 0.95,
|
| 69 |
+
"sentiment": "positive"
|
| 70 |
+
}
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### POST `/predict_proba`
|
| 74 |
+
```json
|
| 75 |
+
{
|
| 76 |
+
"text": "This is terrible"
|
| 77 |
+
}
|
| 78 |
+
```
|
| 79 |
+
Response:
|
| 80 |
+
```json
|
| 81 |
+
{
|
| 82 |
+
"probabilities": [0.85, 0.15],
|
| 83 |
+
"prediction": 0,
|
| 84 |
+
"sentiment": "negative"
|
| 85 |
+
}
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### POST `/batch_predict`
|
| 89 |
+
```json
|
| 90 |
+
["I love it!", "This is bad", "It's okay"]
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
## 🔧 Customization
|
| 94 |
+
|
| 95 |
+
### If your model requires text preprocessing:
|
| 96 |
+
Edit the `app.py` file in the prediction functions to add your preprocessing steps:
|
| 97 |
+
|
| 98 |
+
```python
|
| 99 |
+
# Add preprocessing before model.predict()
|
| 100 |
+
def preprocess_text(text):
|
| 101 |
+
# Add your preprocessing logic here
|
| 102 |
+
# e.g., tokenization, vectorization, etc.
|
| 103 |
+
return processed_text
|
| 104 |
+
|
| 105 |
+
# In the prediction functions:
|
| 106 |
+
processed_text = preprocess_text(input_data.text)
|
| 107 |
+
prediction = model.predict([processed_text])[0]
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### To change the port or host:
|
| 111 |
+
Modify the last line in `app.py`:
|
| 112 |
+
```python
|
| 113 |
+
uvicorn.run(app, host="0.0.0.0", port=8000) # Change port as needed
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
## 🆘 Troubleshooting
|
| 117 |
+
|
| 118 |
+
1. **Model loading errors**: Ensure `sentiment.pkl` is in the same directory
|
| 119 |
+
2. **Dependency issues**: Check that all required packages are in `requirements.txt`
|
| 120 |
+
3. **Memory issues**: Your model might be too large for the free tier (upgrade to paid tier)
|
| 121 |
+
4. **Preprocessing errors**: Make sure your text preprocessing matches your training pipeline
|
| 122 |
+
|
| 123 |
+
## 💡 Next Steps
|
| 124 |
+
|
| 125 |
+
- Add authentication for production use
|
| 126 |
+
- Implement rate limiting
|
| 127 |
+
- Add logging and monitoring
|
| 128 |
+
- Create more sophisticated web interface
|
| 129 |
+
- Add model versioning
|
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9
|
| 2 |
+
|
| 3 |
+
WORKDIR /code
|
| 4 |
+
|
| 5 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 6 |
+
|
| 7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 8 |
+
|
| 9 |
+
COPY . /code
|
| 10 |
+
|
| 11 |
+
EXPOSE 7860
|
| 12 |
+
|
| 13 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,10 +1,65 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: Sentiment
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Sentiment Analysis API
|
| 3 |
+
emoji: 😊
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# Sentiment Analysis API
|
| 12 |
+
|
| 13 |
+
A FastAPI-based sentiment analysis service that predicts sentiment (positive/negative) from text input.
|
| 14 |
+
|
| 15 |
+
## Features
|
| 16 |
+
|
| 17 |
+
- **Sentiment Prediction**: Get integer predictions (0 for negative, 1 for positive)
|
| 18 |
+
- **Probability Scores**: Get prediction probabilities for both classes
|
| 19 |
+
- **Batch Processing**: Analyze multiple texts at once
|
| 20 |
+
- **Interactive API**: Swagger UI documentation available at `/docs`
|
| 21 |
+
|
| 22 |
+
## API Endpoints
|
| 23 |
+
|
| 24 |
+
### `/predict`
|
| 25 |
+
- **Method**: POST
|
| 26 |
+
- **Input**: JSON with `text` field
|
| 27 |
+
- **Output**: Prediction, confidence score, and sentiment label
|
| 28 |
+
|
| 29 |
+
### `/predict_proba`
|
| 30 |
+
- **Method**: POST
|
| 31 |
+
- **Input**: JSON with `text` field
|
| 32 |
+
- **Output**: Probability array, prediction, and sentiment label
|
| 33 |
+
|
| 34 |
+
### `/batch_predict`
|
| 35 |
+
- **Method**: POST
|
| 36 |
+
- **Input**: Array of text strings
|
| 37 |
+
- **Output**: Results for all input texts
|
| 38 |
+
|
| 39 |
+
## Usage Example
|
| 40 |
+
|
| 41 |
+
```python
|
| 42 |
+
import requests
|
| 43 |
+
|
| 44 |
+
# Single prediction
|
| 45 |
+
response = requests.post(
|
| 46 |
+
"https://your-space-url/predict",
|
| 47 |
+
json={"text": "I love this movie!"}
|
| 48 |
+
)
|
| 49 |
+
print(response.json())
|
| 50 |
+
# Output: {"prediction": 1, "confidence": 0.95, "sentiment": "positive"}
|
| 51 |
+
|
| 52 |
+
# Probability prediction
|
| 53 |
+
response = requests.post(
|
| 54 |
+
"https://your-space-url/predict_proba",
|
| 55 |
+
json={"text": "This is terrible"}
|
| 56 |
+
)
|
| 57 |
+
print(response.json())
|
| 58 |
+
# Output: {"probabilities": [0.85, 0.15], "prediction": 0, "sentiment": "negative"}
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## Local Development
|
| 62 |
+
|
| 63 |
+
1. Install dependencies: `pip install -r requirements.txt`
|
| 64 |
+
2. Run the app: `python app.py`
|
| 65 |
+
3. Visit `http://localhost:7860/docs` for interactive API documentation
|
app.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
import numpy as np
|
| 3 |
+
from fastapi import FastAPI, HTTPException
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from typing import Dict, List
|
| 6 |
+
import uvicorn
|
| 7 |
+
|
| 8 |
+
# Initialize FastAPI app
|
| 9 |
+
app = FastAPI(
|
| 10 |
+
title="Sentiment Analysis API",
|
| 11 |
+
description="A sentiment analysis model that predicts sentiment (positive/negative) from text",
|
| 12 |
+
version="1.0.0"
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
# Load the model
|
| 16 |
+
try:
|
| 17 |
+
with open("sentiment.pkl", "rb") as f:
|
| 18 |
+
model = pickle.load(f)
|
| 19 |
+
except FileNotFoundError:
|
| 20 |
+
raise Exception("sentiment.pkl not found. Please ensure the model file is in the correct location.")
|
| 21 |
+
|
| 22 |
+
# Pydantic models for request/response
|
| 23 |
+
class TextInput(BaseModel):
|
| 24 |
+
text: str
|
| 25 |
+
|
| 26 |
+
class PredictionResponse(BaseModel):
|
| 27 |
+
prediction: int
|
| 28 |
+
confidence: float
|
| 29 |
+
sentiment: str
|
| 30 |
+
|
| 31 |
+
class ProbabilityResponse(BaseModel):
|
| 32 |
+
probabilities: List[float]
|
| 33 |
+
prediction: int
|
| 34 |
+
sentiment: str
|
| 35 |
+
|
| 36 |
+
@app.get("/")
|
| 37 |
+
async def root():
|
| 38 |
+
return {
|
| 39 |
+
"message": "Sentiment Analysis API",
|
| 40 |
+
"endpoints": {
|
| 41 |
+
"/predict": "Get sentiment prediction (0 or 1)",
|
| 42 |
+
"/predict_proba": "Get prediction probabilities",
|
| 43 |
+
"/health": "Check API health"
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
@app.get("/health")
|
| 48 |
+
async def health_check():
|
| 49 |
+
return {"status": "healthy", "model_loaded": True}
|
| 50 |
+
|
| 51 |
+
@app.post("/predict", response_model=PredictionResponse)
|
| 52 |
+
async def predict_sentiment(input_data: TextInput):
|
| 53 |
+
"""
|
| 54 |
+
Predict sentiment from text input.
|
| 55 |
+
Returns integer prediction (0 for negative, 1 for positive).
|
| 56 |
+
"""
|
| 57 |
+
try:
|
| 58 |
+
# Convert text to the format expected by your model
|
| 59 |
+
# Note: You may need to preprocess the text depending on your model's requirements
|
| 60 |
+
text_data = [input_data.text]
|
| 61 |
+
|
| 62 |
+
# Get prediction
|
| 63 |
+
prediction = model.predict(text_data)[0]
|
| 64 |
+
|
| 65 |
+
# Get probabilities to calculate confidence
|
| 66 |
+
probabilities = model.predict_proba(text_data)[0]
|
| 67 |
+
confidence = float(max(probabilities))
|
| 68 |
+
|
| 69 |
+
# Convert prediction to sentiment label
|
| 70 |
+
sentiment = "positive" if prediction == 1 else "negative"
|
| 71 |
+
|
| 72 |
+
return PredictionResponse(
|
| 73 |
+
prediction=int(prediction),
|
| 74 |
+
confidence=confidence,
|
| 75 |
+
sentiment=sentiment
|
| 76 |
+
)
|
| 77 |
+
except Exception as e:
|
| 78 |
+
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
|
| 79 |
+
|
| 80 |
+
@app.post("/predict_proba", response_model=ProbabilityResponse)
|
| 81 |
+
async def predict_probabilities(input_data: TextInput):
|
| 82 |
+
"""
|
| 83 |
+
Get prediction probabilities for sentiment analysis.
|
| 84 |
+
Returns probability arrays with shape (n_samples, 2).
|
| 85 |
+
"""
|
| 86 |
+
try:
|
| 87 |
+
# Convert text to the format expected by your model
|
| 88 |
+
text_data = [input_data.text]
|
| 89 |
+
|
| 90 |
+
# Get probabilities
|
| 91 |
+
probabilities = model.predict_proba(text_data)[0]
|
| 92 |
+
prediction = model.predict(text_data)[0]
|
| 93 |
+
|
| 94 |
+
# Convert prediction to sentiment label
|
| 95 |
+
sentiment = "positive" if prediction == 1 else "negative"
|
| 96 |
+
|
| 97 |
+
return ProbabilityResponse(
|
| 98 |
+
probabilities=probabilities.tolist(),
|
| 99 |
+
prediction=int(prediction),
|
| 100 |
+
sentiment=sentiment
|
| 101 |
+
)
|
| 102 |
+
except Exception as e:
|
| 103 |
+
raise HTTPException(status_code=500, detail=f"Probability prediction failed: {str(e)}")
|
| 104 |
+
|
| 105 |
+
@app.post("/batch_predict")
|
| 106 |
+
async def batch_predict(texts: List[str]):
|
| 107 |
+
"""
|
| 108 |
+
Predict sentiment for multiple texts at once.
|
| 109 |
+
"""
|
| 110 |
+
try:
|
| 111 |
+
predictions = model.predict(texts)
|
| 112 |
+
probabilities = model.predict_proba(texts)
|
| 113 |
+
|
| 114 |
+
results = []
|
| 115 |
+
for i, text in enumerate(texts):
|
| 116 |
+
sentiment = "positive" if predictions[i] == 1 else "negative"
|
| 117 |
+
results.append({
|
| 118 |
+
"text": text,
|
| 119 |
+
"prediction": int(predictions[i]),
|
| 120 |
+
"probabilities": probabilities[i].tolist(),
|
| 121 |
+
"sentiment": sentiment,
|
| 122 |
+
"confidence": float(max(probabilities[i]))
|
| 123 |
+
})
|
| 124 |
+
|
| 125 |
+
return {"results": results}
|
| 126 |
+
except Exception as e:
|
| 127 |
+
raise HTTPException(status_code=500, detail=f"Batch prediction failed: {str(e)}")
|
| 128 |
+
|
| 129 |
+
if __name__ == "__main__":
|
| 130 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
index.html
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Sentiment Analysis</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
font-family: Arial, sans-serif;
|
| 10 |
+
max-width: 800px;
|
| 11 |
+
margin: 0 auto;
|
| 12 |
+
padding: 20px;
|
| 13 |
+
background-color: #f5f5f5;
|
| 14 |
+
}
|
| 15 |
+
.container {
|
| 16 |
+
background-color: white;
|
| 17 |
+
padding: 30px;
|
| 18 |
+
border-radius: 10px;
|
| 19 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
| 20 |
+
}
|
| 21 |
+
h1 {
|
| 22 |
+
color: #333;
|
| 23 |
+
text-align: center;
|
| 24 |
+
margin-bottom: 30px;
|
| 25 |
+
}
|
| 26 |
+
textarea {
|
| 27 |
+
width: 100%;
|
| 28 |
+
min-height: 120px;
|
| 29 |
+
padding: 15px;
|
| 30 |
+
border: 2px solid #ddd;
|
| 31 |
+
border-radius: 5px;
|
| 32 |
+
font-size: 16px;
|
| 33 |
+
resize: vertical;
|
| 34 |
+
}
|
| 35 |
+
button {
|
| 36 |
+
background-color: #007bff;
|
| 37 |
+
color: white;
|
| 38 |
+
padding: 12px 24px;
|
| 39 |
+
border: none;
|
| 40 |
+
border-radius: 5px;
|
| 41 |
+
cursor: pointer;
|
| 42 |
+
font-size: 16px;
|
| 43 |
+
margin: 10px 5px;
|
| 44 |
+
}
|
| 45 |
+
button:hover {
|
| 46 |
+
background-color: #0056b3;
|
| 47 |
+
}
|
| 48 |
+
.result {
|
| 49 |
+
margin-top: 20px;
|
| 50 |
+
padding: 15px;
|
| 51 |
+
border-radius: 5px;
|
| 52 |
+
font-weight: bold;
|
| 53 |
+
}
|
| 54 |
+
.positive {
|
| 55 |
+
background-color: #d4edda;
|
| 56 |
+
color: #155724;
|
| 57 |
+
border: 1px solid #c3e6cb;
|
| 58 |
+
}
|
| 59 |
+
.negative {
|
| 60 |
+
background-color: #f8d7da;
|
| 61 |
+
color: #721c24;
|
| 62 |
+
border: 1px solid #f5c6cb;
|
| 63 |
+
}
|
| 64 |
+
.confidence {
|
| 65 |
+
margin-top: 10px;
|
| 66 |
+
font-size: 14px;
|
| 67 |
+
}
|
| 68 |
+
.api-info {
|
| 69 |
+
margin-top: 30px;
|
| 70 |
+
padding: 20px;
|
| 71 |
+
background-color: #e9ecef;
|
| 72 |
+
border-radius: 5px;
|
| 73 |
+
}
|
| 74 |
+
</style>
|
| 75 |
+
</head>
|
| 76 |
+
<body>
|
| 77 |
+
<div class="container">
|
| 78 |
+
<h1>🎭 Sentiment Analysis</h1>
|
| 79 |
+
<p>Enter your text below to analyze its sentiment:</p>
|
| 80 |
+
|
| 81 |
+
<textarea id="textInput" placeholder="Type your text here...">I love this new product! It's amazing and works perfectly.</textarea>
|
| 82 |
+
|
| 83 |
+
<div style="text-align: center;">
|
| 84 |
+
<button onclick="predictSentiment()">Predict Sentiment</button>
|
| 85 |
+
<button onclick="getProbabilities()">Get Probabilities</button>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<div id="result"></div>
|
| 89 |
+
|
| 90 |
+
<div class="api-info">
|
| 91 |
+
<h3>API Endpoints:</h3>
|
| 92 |
+
<ul>
|
| 93 |
+
<li><strong>/predict</strong> - Get sentiment prediction (0 or 1)</li>
|
| 94 |
+
<li><strong>/predict_proba</strong> - Get prediction probabilities</li>
|
| 95 |
+
<li><strong>/docs</strong> - Interactive API documentation</li>
|
| 96 |
+
</ul>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<script>
|
| 101 |
+
async function predictSentiment() {
|
| 102 |
+
const text = document.getElementById('textInput').value;
|
| 103 |
+
if (!text.trim()) {
|
| 104 |
+
alert('Please enter some text');
|
| 105 |
+
return;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
try {
|
| 109 |
+
const response = await fetch('/predict', {
|
| 110 |
+
method: 'POST',
|
| 111 |
+
headers: {
|
| 112 |
+
'Content-Type': 'application/json',
|
| 113 |
+
},
|
| 114 |
+
body: JSON.stringify({ text: text })
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
const result = await response.json();
|
| 118 |
+
displayResult(result, 'prediction');
|
| 119 |
+
} catch (error) {
|
| 120 |
+
console.error('Error:', error);
|
| 121 |
+
displayError('Error making prediction');
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
async function getProbabilities() {
|
| 126 |
+
const text = document.getElementById('textInput').value;
|
| 127 |
+
if (!text.trim()) {
|
| 128 |
+
alert('Please enter some text');
|
| 129 |
+
return;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
try {
|
| 133 |
+
const response = await fetch('/predict_proba', {
|
| 134 |
+
method: 'POST',
|
| 135 |
+
headers: {
|
| 136 |
+
'Content-Type': 'application/json',
|
| 137 |
+
},
|
| 138 |
+
body: JSON.stringify({ text: text })
|
| 139 |
+
});
|
| 140 |
+
|
| 141 |
+
const result = await response.json();
|
| 142 |
+
displayResult(result, 'probability');
|
| 143 |
+
} catch (error) {
|
| 144 |
+
console.error('Error:', error);
|
| 145 |
+
displayError('Error getting probabilities');
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
function displayResult(result, type) {
|
| 150 |
+
const resultDiv = document.getElementById('result');
|
| 151 |
+
const sentimentClass = result.sentiment === 'positive' ? 'positive' : 'negative';
|
| 152 |
+
|
| 153 |
+
let html = `<div class="result ${sentimentClass}">
|
| 154 |
+
<div>Sentiment: ${result.sentiment.toUpperCase()} (${result.prediction})</div>`;
|
| 155 |
+
|
| 156 |
+
if (type === 'prediction') {
|
| 157 |
+
html += `<div class="confidence">Confidence: ${(result.confidence * 100).toFixed(1)}%</div>`;
|
| 158 |
+
} else if (type === 'probability') {
|
| 159 |
+
html += `<div class="confidence">
|
| 160 |
+
Probabilities: Negative ${(result.probabilities[0] * 100).toFixed(1)}%,
|
| 161 |
+
Positive ${(result.probabilities[1] * 100).toFixed(1)}%
|
| 162 |
+
</div>`;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
html += `</div>`;
|
| 166 |
+
resultDiv.innerHTML = html;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
function displayError(message) {
|
| 170 |
+
const resultDiv = document.getElementById('result');
|
| 171 |
+
resultDiv.innerHTML = `<div class="result" style="background-color: #f8d7da; color: #721c24;">${message}</div>`;
|
| 172 |
+
}
|
| 173 |
+
</script>
|
| 174 |
+
</body>
|
| 175 |
+
</html>
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn[standard]==0.24.0
|
| 3 |
+
pydantic==2.5.0
|
| 4 |
+
numpy==1.24.3
|
| 5 |
+
scikit-learn==1.3.0
|
| 6 |
+
python-multipart==0.0.6
|
sentiment.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:83e4eacef6ebc4ac101fdb74d36654ec1e74e1918b883089ffb75e993be69bf9
|
| 3 |
+
size 248173794
|
test_api.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
# Test the API endpoints
|
| 5 |
+
base_url = "http://localhost:7860"
|
| 6 |
+
|
| 7 |
+
def test_api():
|
| 8 |
+
# Test data
|
| 9 |
+
test_texts = [
|
| 10 |
+
"I love this product! It's amazing!",
|
| 11 |
+
"This is terrible and disappointing.",
|
| 12 |
+
"It's okay, nothing special.",
|
| 13 |
+
"Best purchase ever! Highly recommend!"
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
print("🧪 Testing Sentiment Analysis API")
|
| 17 |
+
print("=" * 50)
|
| 18 |
+
|
| 19 |
+
# Test single prediction
|
| 20 |
+
print("\n📊 Testing /predict endpoint:")
|
| 21 |
+
for text in test_texts[:2]:
|
| 22 |
+
try:
|
| 23 |
+
response = requests.post(
|
| 24 |
+
f"{base_url}/predict",
|
| 25 |
+
json={"text": text}
|
| 26 |
+
)
|
| 27 |
+
result = response.json()
|
| 28 |
+
print(f"Text: '{text}'")
|
| 29 |
+
print(f"Prediction: {result['prediction']} ({result['sentiment']})")
|
| 30 |
+
print(f"Confidence: {result['confidence']:.2%}")
|
| 31 |
+
print("-" * 30)
|
| 32 |
+
except Exception as e:
|
| 33 |
+
print(f"Error testing '{text}': {e}")
|
| 34 |
+
|
| 35 |
+
# Test probability prediction
|
| 36 |
+
print("\n📈 Testing /predict_proba endpoint:")
|
| 37 |
+
test_text = test_texts[0]
|
| 38 |
+
try:
|
| 39 |
+
response = requests.post(
|
| 40 |
+
f"{base_url}/predict_proba",
|
| 41 |
+
json={"text": test_text}
|
| 42 |
+
)
|
| 43 |
+
result = response.json()
|
| 44 |
+
print(f"Text: '{test_text}'")
|
| 45 |
+
print(f"Probabilities: {result['probabilities']}")
|
| 46 |
+
print(f"Prediction: {result['prediction']} ({result['sentiment']})")
|
| 47 |
+
print("-" * 30)
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Error testing probabilities: {e}")
|
| 50 |
+
|
| 51 |
+
# Test batch prediction
|
| 52 |
+
print("\n📦 Testing /batch_predict endpoint:")
|
| 53 |
+
try:
|
| 54 |
+
response = requests.post(
|
| 55 |
+
f"{base_url}/batch_predict",
|
| 56 |
+
json=test_texts
|
| 57 |
+
)
|
| 58 |
+
results = response.json()
|
| 59 |
+
for result in results['results']:
|
| 60 |
+
print(f"Text: '{result['text']}'")
|
| 61 |
+
print(f"Sentiment: {result['sentiment']} (confidence: {result['confidence']:.2%})")
|
| 62 |
+
print("-" * 30)
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f"Error testing batch prediction: {e}")
|
| 65 |
+
|
| 66 |
+
if __name__ == "__main__":
|
| 67 |
+
print("Make sure to start the API server first with: python app.py")
|
| 68 |
+
print("Then run this test script.")
|
| 69 |
+
|
| 70 |
+
# Uncomment the line below to run tests (make sure API is running first)
|
| 71 |
+
# test_api()
|