Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 19 additions & 38 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: 3.9

Expand Down Expand Up @@ -57,29 +57,13 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: [36, 37, 38, 39, 310, 311, 312]
cibw-arch: [auto, aarch64]
exclude:
- os: macos-latest
cibw-arch: aarch64
- os: windows-latest
cibw-arch: aarch64
os: [macos-latest, ubuntu-latest, windows-latest, ubuntu-24.04-arm]

steps:
- uses: actions/checkout@v3

- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v3
with:
platforms: all
- uses: actions/checkout@v6

- name: Build wheels
uses: pypa/cibuildwheel@v2.16.2
env:
CIBW_BUILD: cp${{matrix.python-version}}-*
CIBW_ARCHS_LINUX: ${{ matrix.cibw-arch }}
uses: pypa/cibuildwheel@v3.4.1

- name: Build sdist
run: |
Expand All @@ -93,42 +77,38 @@ jobs:
if: runner.os == 'Linux'

- name: Upload Binaries
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v7
with:
name: wheels
path: wheelhouse
name: wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl

test-wheels:
needs: [build-wheels]
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12']
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14']
os: [macos-latest, ubuntu-latest, windows-latest, ubuntu-24.04-arm]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
path: implicit_source
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v8
with:
name: wheels
name: wheels-${{ matrix.os }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
pip install -r implicit_source/requirements.txt
- name: Install h5py
run: pip install h5py

- name: Install ANN Libraries
run: pip install annoy nmslib
if: ${{ matrix.python-version != '3.12' && matrix.python-version != '3.11' && runner.os == 'Linux' }}

if: ${{ matrix.python-version != '3.14' && runner.os == 'Linux' }}
- name: Install wheel
run: |
pip install --force-reinstall --no-deps --no-index --find-links . implicit
Expand All @@ -142,15 +122,16 @@ jobs:
if: "startsWith(github.ref, 'refs/tags/')"
needs: [test-wheels]
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v8
with:
name: wheels
pattern: wheels-*
merge-multiple: true
- name: Create GitHub Release
uses: fnkr/github-action-ghr@v1.3
env:
GHR_PATH: .
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-python@v2
- uses: actions/setup-python@v6
with:
python-version: 3.9
- name: Push to PyPi
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
repos:
- repo: https://github.com/timothycrosley/isort
rev: 5.12.0
rev: 8.0.1
hooks:
- id: isort
additional_dependencies: [toml]
- repo: https://github.com/python/black
rev: 23.3.0
rev: 26.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 7.3.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/pylint
rev: v2.17.4
rev: v4.0.5
hooks:
- id: pylint
- repo: https://github.com/codespell-project/codespell
Expand Down
3 changes: 3 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ disable=fixme,
min-similarity-lines=64
ignore-docstrings=yes
ignore-imports=yes

[DESIGN]
max-positional-arguments=12
3 changes: 2 additions & 1 deletion benchmarks/benchmark_als.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" test script to verify the CG method works, and time it versus cholesky """
"""test script to verify the CG method works, and time it versus cholesky"""

import argparse
import json
import logging
Expand Down
8 changes: 4 additions & 4 deletions examples/lastfm.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
""" An example of using this library to calculate related artists
"""An example of using this library to calculate related artists
from the last.fm dataset. More details can be found
at http://www.benfrederickson.com/matrix-factorization/

This code will automatically download a HDF5 version of the dataset from
GitHub when it is first run. The original dataset can also be found at
http://ocelma.net/MusicRecommendationDataset/lastfm-360K.html
"""

import argparse
import codecs
import logging
import time

Expand Down Expand Up @@ -102,7 +102,7 @@ def calculate_similar_artists(output_filename, model_name="als"):
# write out as a TSV of artistid, otherartistid, score
logging.debug("writing similar items")
with tqdm.tqdm(total=len(to_generate)) as progress:
with codecs.open(output_filename, "w", "utf8") as o:
with open(output_filename, "w", encoding="utf8") as o:
batch_size = 1000
for startidx in range(0, len(to_generate), batch_size):
batch = to_generate[startidx : startidx + batch_size]
Expand Down Expand Up @@ -146,7 +146,7 @@ def calculate_recommendations(output_filename, model_name="als"):
# generate recommendations for each user and write out to a file
start = time.time()
with tqdm.tqdm(total=len(users)) as progress:
with codecs.open(output_filename, "w", "utf8") as o:
with open(output_filename, "w", encoding="utf8") as o:
batch_size = 1000
to_generate = np.arange(len(users))
for startidx in range(0, len(to_generate), batch_size):
Expand Down
3 changes: 1 addition & 2 deletions examples/movielens.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from __future__ import print_function

import argparse
import codecs
import logging
import time

Expand Down Expand Up @@ -87,7 +86,7 @@ def calculate_similar_movies(output_filename, model_name="als", min_rating=4.0,

log.debug("calculating similar movies")
with tqdm.tqdm(total=len(to_generate)) as progress:
with codecs.open(output_filename, "w", "utf8") as o:
with open(output_filename, "w", encoding="utf8") as o:
batch_size = 1000
for startidx in range(0, len(to_generate), batch_size):
batch = to_generate[startidx : startidx + batch_size]
Expand Down
2 changes: 1 addition & 1 deletion examples/tutorial_lastfm.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
"# Use pandas to display the output in a table, pandas isn't a dependency of implicit otherwise\n",
"import numpy as np\n",
"import pandas as pd\n",
"pd.DataFrame({\"artist\": artists[ids], \"score\": scores, \"already_liked\": np.in1d(ids, user_plays[userid].indices)})"
"pd.DataFrame({\"artist\": artists[ids], \"score\": scores, \"already_liked\": np.isin(ids, user_plays[userid].indices)})"
]
},
{
Expand Down
5 changes: 3 additions & 2 deletions implicit/_nearest_neighbours.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ from cython cimport floating, integral
from cython.operator import dereference
from cython.parallel import parallel, prange

from libc.stdint cimport int32_t
from libcpp cimport bool
from libcpp.algorithm cimport sort_heap
from libcpp.utility cimport pair
Expand Down Expand Up @@ -131,8 +132,8 @@ def all_pairs_knn(users, unsigned int K=100, int num_threads=0, show_progress=Tr

# holds triples of output
cdef double[:] values = np.zeros(item_count * K)
cdef long[:] rows = np.zeros(item_count * K, dtype=int)
cdef long[:] cols = np.zeros(item_count * K, dtype=int)
cdef int32_t[:] rows = np.zeros(item_count * K, dtype="int32")
cdef int32_t[:] cols = np.zeros(item_count * K, dtype="int32")

progress = tqdm(total=item_count, disable=not show_progress)
with nogil, parallel(num_threads=num_threads):
Expand Down
1 change: 0 additions & 1 deletion implicit/ann/annoy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


class AnnoyModel(RecommenderBase):

"""Speeds up inference calls to MatrixFactorization models by using an
`Annoy <https://github.com/spotify/annoy>`_ index to calculate similar items and
recommend items.
Expand Down
1 change: 0 additions & 1 deletion implicit/ann/nmslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


class NMSLibModel(RecommenderBase):

"""Speeds up inference calls to MatrixFactorization models by using
`NMSLib <https://github.com/nmslib/nmslib>`_ to create approximate nearest neighbours
indices of the latent factors.
Expand Down
3 changes: 2 additions & 1 deletion implicit/approximate_als.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
""" Models that use various Approximate Nearest Neighbours libraries in order to quickly
"""Models that use various Approximate Nearest Neighbours libraries in order to quickly
generate recommendations and lists of similar items.

See http://www.benfrederickson.com/approximate-nearest-neighbours-for-recommender-systems/
"""

import implicit.gpu


Expand Down
3 changes: 2 additions & 1 deletion implicit/cpu/als.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Implicit Alternating Least Squares """
"""Implicit Alternating Least Squares"""

import functools
import heapq
import logging
Expand Down
3 changes: 2 additions & 1 deletion implicit/cpu/matrix_factorization_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Base class for recommendation algorithms in this package """
"""Base class for recommendation algorithms in this package"""

import warnings

import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion implicit/evaluation.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ def ranking_metrics_at_k(model, train_user_items, test_user_items, int K=10,

while start_idx < len(to_generate):
batch = to_generate[start_idx: start_idx + batch_size]
ids, _ = model.recommend(batch, train_user_items[batch], N=K)
ids, _ = model.recommend(batch, train_user_items[np.asarray(batch)], N=K)
start_idx += batch_size

with nogil:
Expand Down
12 changes: 6 additions & 6 deletions implicit/nearest_neighbours.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ def recommend(
)

if filter_items is not None:
mask = np.in1d(ids, filter_items, invert=True)
mask = np.isin(ids, filter_items, invert=True)
ids, scores = ids[mask][:N], scores[mask][:N]

elif items is not None:
mask = np.in1d(ids, items)
mask = np.isin(ids, items)
ids, scores = ids[mask], scores[mask]

# returned items should be equal to input selected items
missing = items[np.in1d(items, ids, invert=True)]
missing = items[np.isin(items, ids, invert=True)]
if missing.size:
ids = np.append(ids, missing)
scores = np.append(scores, np.full(missing.size, -np.finfo(scores.dtype).max))
Expand Down Expand Up @@ -128,15 +128,15 @@ def similar_items(
scores = self.similarity[itemid].data

if filter_items is not None:
mask = np.in1d(ids, filter_items, invert=True)
mask = np.isin(ids, filter_items, invert=True)
ids, scores = ids[mask], scores[mask]

elif items is not None:
mask = np.in1d(ids, items)
mask = np.isin(ids, items)
ids, scores = ids[mask], scores[mask]

# returned items should be equal to input selected items
missing = items[np.in1d(items, ids, invert=True)]
missing = items[np.isin(items, ids, invert=True)]
if missing.size:
ids = np.append(ids, missing)
scores = np.append(scores, np.full(missing.size, -np.finfo(scores.dtype).max))
Expand Down
3 changes: 2 additions & 1 deletion implicit/recommender_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Base class for recommendation algorithms in this package """
"""Base class for recommendation algorithms in this package"""

import warnings
from abc import ABCMeta, abstractmethod

Expand Down
4 changes: 2 additions & 2 deletions implicit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ def _batch_call(func, ids, *args, N=10, **kwargs):

def _filter_items_from_results(queryid, ids, scores, filter_items, N):
if np.isscalar(queryid):
mask = np.in1d(ids, filter_items, invert=True)
mask = np.isin(ids, filter_items, invert=True)
ids, scores = ids[mask][:N], scores[mask][:N]
else:
rows = len(queryid)
filtered_scores = np.zeros((rows, N), dtype=scores.dtype)
filtered_ids = np.zeros((rows, N), dtype=ids.dtype)
for row in range(rows):
mask = np.in1d(ids[row], filter_items, invert=True)
mask = np.isin(ids[row], filter_items, invert=True)
filtered_ids[row] = ids[row][mask][:N]
filtered_scores[row] = scores[row][mask][:N]
ids, scores = filtered_ids, filtered_scores
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ build-backend = "setuptools.build_meta"
# skip testing in the cibuildwheel phase, will install the wheels later
# and verify
test-command = ""
skip = ["pp*", "*musl*", "*-manylinux_i686", "*win32"]
skip = ["*t-*", "*musl*", "*-manylinux_i686", "*win32"]

[[tool.cibuildwheel.overrides]]
select = "*-manylinux_x86_64*"
Expand Down
3 changes: 2 additions & 1 deletion tests/recommender_base_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Common test functions for all recommendation models """
"""Common test functions for all recommendation models"""

import os
import pickle
import random
Expand Down
Loading